diff --git a/package.json b/package.json index ee9102659..923bab979 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "node-sass", "version": "4.13.1", - "libsass": "3.5.4", + "libsass": "3.6.3", "description": "Wrapper around libsass", "license": "MIT", "bugs": "https://github.com/sass/node-sass/issues", diff --git a/src/libsass.gyp b/src/libsass.gyp index add96e89a..813379fd2 100644 --- a/src/libsass.gyp +++ b/src/libsass.gyp @@ -11,12 +11,21 @@ 'DEBUG' ], 'sources': [ + 'libsass/src/ast2c.cpp', 'libsass/src/ast.cpp', 'libsass/src/ast_fwd_decl.cpp', + 'libsass/src/ast_sel_cmp.cpp', + 'libsass/src/ast_selectors.cpp', + 'libsass/src/ast_sel_super.cpp', + 'libsass/src/ast_sel_unify.cpp', + 'libsass/src/ast_sel_weave.cpp', + 'libsass/src/ast_supports.cpp', + 'libsass/src/ast_values.cpp', 'libsass/src/backtrace.cpp', 'libsass/src/base64vlq.cpp', 'libsass/src/bind.cpp', 'libsass/src/cencode.c', + 'libsass/src/c2ast.cpp', 'libsass/src/check_nesting.cpp', 'libsass/src/color_maps.cpp', 'libsass/src/constants.cpp', @@ -26,37 +35,45 @@ 'libsass/src/environment.cpp', 'libsass/src/error_handling.cpp', 'libsass/src/eval.cpp', + 'libsass/src/eval_selectors.cpp', 'libsass/src/expand.cpp', - 'libsass/src/extend.cpp', + 'libsass/src/extender.cpp', + 'libsass/src/extension.cpp', 'libsass/src/file.cpp', - 'libsass/src/functions.cpp', + 'libsass/src/fn_colors.cpp', + 'libsass/src/fn_lists.cpp', + 'libsass/src/fn_maps.cpp', + 'libsass/src/fn_miscs.cpp', + 'libsass/src/fn_numbers.cpp', + 'libsass/src/fn_selectors.cpp', + 'libsass/src/fn_strings.cpp', + 'libsass/src/fn_utils.cpp', 'libsass/src/inspect.cpp', 'libsass/src/json.cpp', 'libsass/src/lexer.cpp', 'libsass/src/listize.cpp', 'libsass/src/memory/SharedPtr.cpp', - 'libsass/src/node.cpp', 'libsass/src/operators.cpp', 'libsass/src/operators.hpp', 'libsass/src/output.cpp', 'libsass/src/parser.cpp', + 'libsass/src/parser_selectors.cpp', 'libsass/src/plugins.cpp', 'libsass/src/position.cpp', 'libsass/src/prelexer.cpp', 'libsass/src/remove_placeholders.cpp', - 'libsass/src/sass.cpp', 'libsass/src/sass2scss.cpp', 'libsass/src/sass_context.cpp', + 'libsass/src/sass.cpp', 'libsass/src/sass_functions.cpp', - 'libsass/src/sass_util.cpp', 'libsass/src/sass_values.cpp', 'libsass/src/source_map.cpp', - 'libsass/src/subset_map.cpp', - 'libsass/src/to_c.cpp', + 'libsass/src/stylesheet.cpp', 'libsass/src/to_value.cpp', 'libsass/src/units.cpp', 'libsass/src/utf8_string.cpp', 'libsass/src/util.cpp', + 'libsass/src/util_string.cpp', 'libsass/src/values.cpp' ], 'cflags!': [ diff --git a/src/libsass/.editorconfig b/src/libsass/.editorconfig index c3d859e7a..7729eef55 100644 --- a/src/libsass/.editorconfig +++ b/src/libsass/.editorconfig @@ -10,6 +10,6 @@ insert_final_newline = true indent_style = space indent_size = 2 -[{Makefile, GNUmakefile.am}] +[{Makefile,GNUmakefile.am}] indent_style = tab indent_size = 4 diff --git a/src/libsass/.github/CONTRIBUTING.md b/src/libsass/.github/CONTRIBUTING.md index 2ff99d8bd..ee37049ac 100644 --- a/src/libsass/.github/CONTRIBUTING.md +++ b/src/libsass/.github/CONTRIBUTING.md @@ -32,7 +32,7 @@ direction for corners that lack test coverage). Foremost we rely on good and con ## My error is hiding in a big code base 1. we do not have time to support your code base! -2. to fix occuring issues we need precise bug reports +2. to fix occurring issues we need precise bug reports 3. the more precise you are, the faster we can help you 4. lazy reports get overlooked even when exposing serious bugs 5. it's not hard to do, it only takes time @@ -60,6 +60,6 @@ report. Once you verified all of the above, you may use the template below to fi [6]: http://www.sassmeister.com/ [7]: https://rubygems.org/gems/sass -[8]: http://sass-lang.com/ +[8]: https://sass-lang.com/ [9]: https://github.com/sass/libsass/tree/master/docs [10]: https://github.com/sass/sass-spec diff --git a/src/libsass/.gitignore b/src/libsass/.gitignore index f2ee6beac..ef3ca0220 100644 --- a/src/libsass/.gitignore +++ b/src/libsass/.gitignore @@ -2,6 +2,7 @@ /sassc /sass-spec +/plugins/ VERSION .DS_Store @@ -83,3 +84,13 @@ sassc/ sass-spec/ installer/ + +# Visual Studio cache directory +.vs/ + +# Visual Studio Code +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json diff --git a/src/libsass/.travis.yml b/src/libsass/.travis.yml index 09ca55066..d2a0ca0f4 100644 --- a/src/libsass/.travis.yml +++ b/src/libsass/.travis.yml @@ -1,6 +1,8 @@ language: cpp -sudo: false +# ASan needs ptrace support which currently requires `sudo: required`. +# See https://github.com/travis-ci/travis-ci/issues/9033. +sudo: required # don't create redundant code coverage reports # - AUTOTOOLS=yes COVERAGE=yes BUILD=static @@ -12,7 +14,7 @@ sudo: false # this will still catch all coding errors! # - AUTOTOOLS=yes COVERAGE=no BUILD=static -# currenty there are various issues when +# currently there are various issues when # built with coverage, clang and autotools # - AUTOTOOLS=yes COVERAGE=yes BUILD=shared @@ -31,15 +33,19 @@ matrix: packages: - g++-5 - os: linux - compiler: clang++-3.7 - env: AUTOTOOLS=no COVERAGE=yes BUILD=static + compiler: g++-8 + env: AUTOTOOLS=yes COVERAGE=no BUILD=shared addons: apt: sources: - ubuntu-toolchain-r-test - - llvm-toolchain-precise-3.7 packages: - - clang-3.7 + - g++-8 + - os: linux + compiler: clang + # This build runs with ASan and we set `detect_odr_violation=0` + # to work around https://bugs.llvm.org/show_bug.cgi?id=37545. + env: AUTOTOOLS=no COVERAGE=no BUILD=static ASAN_OPTIONS=detect_odr_violation=0 - os: linux compiler: clang env: AUTOTOOLS=yes COVERAGE=no BUILD=shared @@ -48,7 +54,7 @@ matrix: env: AUTOTOOLS=no COVERAGE=no BUILD=shared - os: osx compiler: clang - env: AUTOTOOLS=no COVERAGE=yes BUILD=static + env: AUTOTOOLS=no COVERAGE=no BUILD=static - os: osx compiler: clang env: AUTOTOOLS=yes COVERAGE=no BUILD=shared diff --git a/src/libsass/CODE_OF_CONDUCT.md b/src/libsass/CODE_OF_CONDUCT.md new file mode 100644 index 000000000..c4164af10 --- /dev/null +++ b/src/libsass/CODE_OF_CONDUCT.md @@ -0,0 +1,10 @@ +Sass is more than a technology; Sass is driven by the community of individuals +that power its development and use every day. As a community, we want to embrace +the very differences that have made our collaboration so powerful, and work +together to provide the best environment for learning, growing, and sharing of +ideas. It is imperative that we keep Sass a fun, welcoming, challenging, and +fair place to play. + +[The full community guidelines can be found on the Sass website.][link] + +[link]: https://sass-lang.com/community-guidelines diff --git a/src/libsass/GNUmakefile.am b/src/libsass/GNUmakefile.am index d197261e7..319d1466e 100644 --- a/src/libsass/GNUmakefile.am +++ b/src/libsass/GNUmakefile.am @@ -18,11 +18,9 @@ AM_LDFLAGS = $(AM_COPT) $(AM_COVLDFLAGS) AM_CPPFLAGS += -I$(top_srcdir) RESOURCES = +AM_CXXFLAGS += -std=c++11 if COMPILER_IS_MINGW32 RESOURCES += res/libsass.rc - AM_CXXFLAGS += -std=gnu++0x -else - AM_CXXFLAGS += -std=c++0x endif TEST_EXTENSIONS = .rb @@ -48,7 +46,7 @@ TESTS = $(SASS_SPEC_PATH)/sass-spec.rb RB_LOG_COMPILER = ./script/tap-runner AM_RB_LOG_FLAGS = $(RUBY) -SASS_TEST_FLAGS = -V 3.5 --impl libsass +SASS_TEST_FLAGS = --impl libsass SASS_TEST_FLAGS += -r $(SASS_SPEC_PATH) SASS_TEST_FLAGS += -c $(top_srcdir)/tester$(EXEEXT) AM_TESTS_ENVIRONMENT = TEST_FLAGS='$(SASS_TEST_FLAGS)' @@ -67,6 +65,9 @@ test_full: test_probe: $(SASS_TESTER) --probe-todo $(SASS_TEST_FLAGS) +test_interactive: + $(SASS_TESTER) --interactive $(SASS_TEST_FLAGS) + .PHONY: test test_build test_full test_probe endif diff --git a/src/libsass/Makefile b/src/libsass/Makefile index f3a294533..e861b79b1 100644 --- a/src/libsass/Makefile +++ b/src/libsass/Makefile @@ -1,6 +1,6 @@ OS ?= $(shell uname -s) -CC ?= gcc -CXX ?= g++ +CC ?= cc +CXX ?= c++ RM ?= rm -f CP ?= cp -a MKDIR ?= mkdir @@ -15,7 +15,7 @@ INSTALL ?= install CFLAGS ?= -Wall CXXFLAGS ?= -Wall LDFLAGS ?= -Wall -ifeq "x$(COVERAGE)" "x" +ifndef COVERAGE CFLAGS += -O2 CXXFLAGS += -O2 LDFLAGS += -O2 @@ -24,66 +24,58 @@ else CXXFLAGS += -O1 -fno-omit-frame-pointer LDFLAGS += -O1 -fno-omit-frame-pointer endif -LDFLAGS += -Wl,-undefined,error -CAT ?= $(if $(filter $(OS),Windows_NT),type,cat) +CAT ?= $(if $(filter $(OS),Windows_NT),type,cat) ifneq (,$(findstring /cygdrive/,$(PATH))) UNAME := Cygwin else - ifneq (,$(findstring Windows_NT,$(OS))) - UNAME := Windows - else - ifneq (,$(findstring mingw32,$(MAKE))) - UNAME := Windows - else - ifneq (,$(findstring MINGW32,$(shell uname -s))) - UNAME = Windows - else - UNAME := $(shell uname -s) - endif - endif - endif +ifneq (,$(findstring Windows_NT,$(OS))) + UNAME := Windows +else +ifneq (,$(findstring mingw32,$(MAKE))) + UNAME := Windows +else +ifneq (,$(findstring MINGW32,$(shell uname -s))) + UNAME := Windows +else + UNAME := $(shell uname -s) +endif +endif endif - -ifeq ($(SASS_LIBSASS_PATH),) - SASS_LIBSASS_PATH = $(abspath $(CURDIR)) endif -ifeq ($(LIBSASS_VERSION),) +ifndef LIBSASS_VERSION ifneq ($(wildcard ./.git/ ),) LIBSASS_VERSION ?= $(shell git describe --abbrev=4 --dirty --always --tags) endif -endif - -ifeq ($(LIBSASS_VERSION),) ifneq ($(wildcard VERSION),) LIBSASS_VERSION ?= $(shell $(CAT) VERSION) endif endif - -ifneq ($(LIBSASS_VERSION),) +ifdef LIBSASS_VERSION CFLAGS += -DLIBSASS_VERSION="\"$(LIBSASS_VERSION)\"" CXXFLAGS += -DLIBSASS_VERSION="\"$(LIBSASS_VERSION)\"" endif -# enable mandatory flag +CXXFLAGS += -std=c++11 +LDFLAGS += -std=c++11 + ifeq (Windows,$(UNAME)) ifneq ($(BUILD),shared) STATIC_ALL ?= 1 endif STATIC_LIBGCC ?= 1 STATIC_LIBSTDCPP ?= 1 - CXXFLAGS += -std=gnu++0x - LDFLAGS += -std=gnu++0x else STATIC_ALL ?= 0 STATIC_LIBGCC ?= 0 STATIC_LIBSTDCPP ?= 0 - CXXFLAGS += -std=c++0x - LDFLAGS += -std=c++0x endif -ifneq ($(SASS_LIBSASS_PATH),) +ifndef SASS_LIBSASS_PATH + SASS_LIBSASS_PATH = $(CURDIR) +endif +ifdef SASS_LIBSASS_PATH CFLAGS += -I $(SASS_LIBSASS_PATH)/include CXXFLAGS += -I $(SASS_LIBSASS_PATH)/include else @@ -92,20 +84,15 @@ else CXXFLAGS += -I include endif -ifneq ($(EXTRA_CFLAGS),) - CFLAGS += $(EXTRA_CFLAGS) -endif -ifneq ($(EXTRA_CXXFLAGS),) - CXXFLAGS += $(EXTRA_CXXFLAGS) -endif -ifneq ($(EXTRA_LDFLAGS),) - LDFLAGS += $(EXTRA_LDFLAGS) -endif +CFLAGS += $(EXTRA_CFLAGS) +CXXFLAGS += $(EXTRA_CXXFLAGS) +LDFLAGS += $(EXTRA_LDFLAGS) LDLIBS = -lm - ifneq ($(BUILD),shared) - LDLIBS += -lstdc++ + ifneq ($(STATIC_LIBSTDCPP),1) + LDLIBS += -lstdc++ + endif endif # link statically into lib @@ -143,7 +130,7 @@ ifeq ($(DEBUG),1) BUILD := debug-$(BUILD) endif -ifeq (,$(TRAVIS_BUILD_DIR)) +ifndef TRAVIS_BUILD_DIR ifeq ($(OS),SunOS) PREFIX ?= /opt/local else @@ -153,54 +140,39 @@ else PREFIX ?= $(TRAVIS_BUILD_DIR) endif - SASS_SASSC_PATH ?= sassc SASS_SPEC_PATH ?= sass-spec SASS_SPEC_SPEC_DIR ?= spec SASSC_BIN = $(SASS_SASSC_PATH)/bin/sassc RUBY_BIN = ruby +RESOURCES = +STATICLIB = lib/libsass.a +SHAREDLIB = lib/libsass.so LIB_STATIC = $(SASS_LIBSASS_PATH)/lib/libsass.a LIB_SHARED = $(SASS_LIBSASS_PATH)/lib/libsass.so - -ifeq (Windows,$(UNAME)) - ifeq (shared,$(BUILD)) - CFLAGS += -D ADD_EXPORTS - CXXFLAGS += -D ADD_EXPORTS - LIB_SHARED = $(SASS_LIBSASS_PATH)/lib/libsass.dll - endif -else - ifneq (Cygwin,$(UNAME)) - CFLAGS += -fPIC - CXXFLAGS += -fPIC - LDFLAGS += -fPIC - endif +ifeq ($(UNAME),Darwin) + SHAREDLIB = lib/libsass.dylib + LIB_SHARED = $(SASS_LIBSASS_PATH)/lib/libsass.dylib endif - ifeq (Windows,$(UNAME)) SASSC_BIN = $(SASS_SASSC_PATH)/bin/sassc.exe -endif - -include Makefile.conf - -RESOURCES = -STATICLIB = lib/libsass.a -SHAREDLIB = lib/libsass.so -ifeq (Windows,$(UNAME)) RESOURCES += res/resource.rc SHAREDLIB = lib/libsass.dll ifeq (shared,$(BUILD)) CFLAGS += -D ADD_EXPORTS CXXFLAGS += -D ADD_EXPORTS + LIB_SHARED = $(SASS_LIBSASS_PATH)/lib/libsass.dll endif else - ifneq (Cygwin,$(UNAME)) - CFLAGS += -fPIC - CXXFLAGS += -fPIC - LDFLAGS += -fPIC - endif +ifneq (Cygwin,$(UNAME)) + CFLAGS += -fPIC + CXXFLAGS += -fPIC + LDFLAGS += -fPIC +endif endif +include Makefile.conf OBJECTS = $(addprefix src/,$(SOURCES:.cpp=.o)) COBJECTS = $(addprefix src/,$(CSOURCES:.c=.o)) RCOBJECTS = $(RESOURCES:.rc=.o) @@ -230,14 +202,19 @@ debug-shared: shared lib: $(MKDIR) lib -lib/libsass.a: lib $(COBJECTS) $(OBJECTS) +lib/libsass.a: $(COBJECTS) $(OBJECTS) | lib $(AR) rcvs $@ $(COBJECTS) $(OBJECTS) -lib/libsass.so: lib $(COBJECTS) $(OBJECTS) +lib/libsass.so: $(COBJECTS) $(OBJECTS) | lib $(CXX) -shared $(LDFLAGS) -o $@ $(COBJECTS) $(OBJECTS) $(LDLIBS) -lib/libsass.dll: lib $(COBJECTS) $(OBJECTS) $(RCOBJECTS) - $(CXX) -shared $(LDFLAGS) -o $@ $(COBJECTS) $(OBJECTS) $(RCOBJECTS) $(LDLIBS) -s -Wl,--subsystem,windows,--out-implib,lib/libsass.a +lib/libsass.dylib: $(COBJECTS) $(OBJECTS) | lib + $(CXX) -shared $(LDFLAGS) -o $@ $(COBJECTS) $(OBJECTS) $(LDLIBS) \ + -install_name @rpath/libsass.dylib + +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 $@ $< @@ -259,18 +236,17 @@ shared: $(SHAREDLIB) $(DESTDIR)$(PREFIX): $(MKDIR) $(DESTDIR)$(PREFIX) -$(DESTDIR)$(PREFIX)/lib: $(DESTDIR)$(PREFIX) +$(DESTDIR)$(PREFIX)/lib: | $(DESTDIR)$(PREFIX) $(MKDIR) $(DESTDIR)$(PREFIX)/lib -$(DESTDIR)$(PREFIX)/include: $(DESTDIR)$(PREFIX) +$(DESTDIR)$(PREFIX)/include: | $(DESTDIR)$(PREFIX) $(MKDIR) $(DESTDIR)$(PREFIX)/include -$(DESTDIR)$(PREFIX)/include/sass: $(DESTDIR)$(PREFIX)/include +$(DESTDIR)$(PREFIX)/include/sass: | $(DESTDIR)$(PREFIX)/include $(MKDIR) $(DESTDIR)$(PREFIX)/include/sass $(DESTDIR)$(PREFIX)/include/%.h: include/%.h \ - $(DESTDIR)$(PREFIX)/include \ - $(DESTDIR)$(PREFIX)/include/sass + | $(DESTDIR)$(PREFIX)/include/sass $(INSTALL) -v -m0644 "$<" "$@" install-headers: $(DESTDIR)$(PREFIX)/include/sass.h \ @@ -282,46 +258,59 @@ install-headers: $(DESTDIR)$(PREFIX)/include/sass.h \ $(DESTDIR)$(PREFIX)/include/sass/functions.h $(DESTDIR)$(PREFIX)/lib/%.a: lib/%.a \ - $(DESTDIR)$(PREFIX)/lib + | $(DESTDIR)$(PREFIX)/lib @$(INSTALL) -v -m0755 "$<" "$@" $(DESTDIR)$(PREFIX)/lib/%.so: lib/%.so \ - $(DESTDIR)$(PREFIX)/lib + | $(DESTDIR)$(PREFIX)/lib @$(INSTALL) -v -m0755 "$<" "$@" $(DESTDIR)$(PREFIX)/lib/%.dll: lib/%.dll \ - $(DESTDIR)$(PREFIX)/lib + | $(DESTDIR)$(PREFIX)/lib + @$(INSTALL) -v -m0755 "$<" "$@" + +$(DESTDIR)$(PREFIX)/lib/%.dylib: lib/%.dylib \ + | $(DESTDIR)$(PREFIX)/lib @$(INSTALL) -v -m0755 "$<" "$@" install-static: $(DESTDIR)$(PREFIX)/lib/libsass.a -install-shared: $(DESTDIR)$(PREFIX)/lib/libsass.so \ +install-shared: $(DESTDIR)$(PREFIX)/$(SHAREDLIB) \ install-headers $(SASSC_BIN): $(BUILD) - $(MAKE) -C $(SASS_SASSC_PATH) build-$(BUILD)-dev + $(MAKE) -C $(SASS_SASSC_PATH) build-$(BUILD) sassc: $(SASSC_BIN) $(SASSC_BIN) -v version: $(SASSC_BIN) - $(SASSC_BIN) -h $(SASSC_BIN) -v -test: $(SASSC_BIN) - $(RUBY_BIN) $(SASS_SPEC_PATH)/sass-spec.rb -V 3.5 -c $(SASSC_BIN) --impl libsass $(LOG_FLAGS) $(SASS_SPEC_PATH)/$(SASS_SPEC_SPEC_DIR) +test: test_build test_build: $(SASSC_BIN) - $(RUBY_BIN) $(SASS_SPEC_PATH)/sass-spec.rb -V 3.5 -c $(SASSC_BIN) --impl libsass $(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 $(SASS_SPEC_PATH)/$(SASS_SPEC_SPEC_DIR)" \ + $(LOG_FLAGS) $(SASS_SPEC_PATH)/$(SASS_SPEC_SPEC_DIR) test_full: $(SASSC_BIN) - $(RUBY_BIN) $(SASS_SPEC_PATH)/sass-spec.rb -V 3.5 -c $(SASSC_BIN) --impl libsass --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 $(SASS_SPEC_PATH)/$(SASS_SPEC_SPEC_DIR)" \ + --run-todo $(LOG_FLAGS) $(SASS_SPEC_PATH)/$(SASS_SPEC_SPEC_DIR) test_probe: $(SASSC_BIN) - $(RUBY_BIN) $(SASS_SPEC_PATH)/sass-spec.rb -V 3.5 -c $(SASSC_BIN) --impl libsass --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 $(SASS_SPEC_PATH)/$(SASS_SPEC_SPEC_DIR)" \ + --probe-todo $(LOG_FLAGS) $(SASS_SPEC_PATH)/$(SASS_SPEC_SPEC_DIR) + +test_interactive: $(SASSC_BIN) + $(RUBY_BIN) $(SASS_SPEC_PATH)/sass-spec.rb -c $(SASSC_BIN) --impl libsass \ + --cmd-args "-I $(SASS_SPEC_PATH)/$(SASS_SPEC_SPEC_DIR)" \ + --interactive $(LOG_FLAGS) $(SASS_SPEC_PATH)/$(SASS_SPEC_SPEC_DIR) -clean-objects: lib - -$(RM) lib/*.a lib/*.so lib/*.dll lib/*.la +clean-objects: | lib + -$(RM) lib/*.a lib/*.so lib/*.dll lib/*.dylib lib/*.la -$(RMDIR) lib clean: clean-objects $(RM) $(CLEANUPS) @@ -347,5 +336,6 @@ lib-opts-shared: debug debug-static debug-shared \ install install-static install-shared \ lib-opts lib-opts-shared lib-opts-static \ - lib-file lib-file-shared lib-file-static + lib-file lib-file-shared lib-file-static \ + test test_build test_full test_probe .DELETE_ON_ERROR: diff --git a/src/libsass/Makefile.conf b/src/libsass/Makefile.conf index 5ba968b68..279830130 100644 --- a/src/libsass/Makefile.conf +++ b/src/libsass/Makefile.conf @@ -7,16 +7,30 @@ SOURCES = \ ast.cpp \ - node.cpp \ + ast_values.cpp \ + ast_supports.cpp \ + ast_sel_cmp.cpp \ + ast_sel_unify.cpp \ + ast_sel_super.cpp \ + ast_sel_weave.cpp \ + ast_selectors.cpp \ context.cpp \ constants.cpp \ - functions.cpp \ + fn_utils.cpp \ + fn_miscs.cpp \ + fn_maps.cpp \ + fn_lists.cpp \ + fn_colors.cpp \ + fn_numbers.cpp \ + fn_strings.cpp \ + fn_selectors.cpp \ color_maps.cpp \ environment.cpp \ ast_fwd_decl.cpp \ bind.cpp \ file.cpp \ util.cpp \ + util_string.cpp \ json.cpp \ units.cpp \ values.cpp \ @@ -24,29 +38,32 @@ SOURCES = \ position.cpp \ lexer.cpp \ parser.cpp \ + parser_selectors.cpp \ prelexer.cpp \ eval.cpp \ + eval_selectors.cpp \ expand.cpp \ listize.cpp \ cssize.cpp \ - extend.cpp \ + extender.cpp \ + extension.cpp \ + stylesheet.cpp \ output.cpp \ inspect.cpp \ emitter.cpp \ check_nesting.cpp \ remove_placeholders.cpp \ sass.cpp \ - sass_util.cpp \ sass_values.cpp \ sass_context.cpp \ sass_functions.cpp \ sass2scss.cpp \ backtrace.cpp \ operators.cpp \ - to_c.cpp \ + ast2c.cpp \ + c2ast.cpp \ to_value.cpp \ source_map.cpp \ - subset_map.cpp \ error_handling.cpp \ memory/SharedPtr.cpp \ utf8_string.cpp \ diff --git a/src/libsass/Readme.md b/src/libsass/Readme.md index 908de2dc4..73555f49b 100644 --- a/src/libsass/Readme.md +++ b/src/libsass/Readme.md @@ -62,8 +62,8 @@ Note that the scripts in the `./script` folder are mainly intended for our CI ne Building -------- -To build LibSass you need GCC 4.6+ or Clang/LLVM. If your OS is older, you may need to upgrade -them first (or install clang as an alternative). On Windows, you need MinGW with GCC 4.6+ or VS 2013 +To build LibSass you need GCC 4.7+ or Clang/LLVM. If your OS is older, you may need to upgrade +them first (or install clang as an alternative). On Windows, you need MinGW with GCC 4.7+ or VS 2013 Update 4+. It is also possible to build LibSass with Clang/LLVM on Windows with various build chains and/or command line interpreters. @@ -72,10 +72,11 @@ See the [build docs for further instructions](docs/build.md)! Compatibility ------------- -Current LibSass 3.4 should be compatible with Sass 3.4. Please refer to the [sass compatibility -page](http://sass-compatibility.github.io/) for a more detailed comparison. But note that there -are still a few incomplete edges which we are aware of. Otherwise LibSass has reached a good level -of stability, thanks to our ever growing [Sass-Spec test suite](https://github.com/sass/sass-spec). +For all intents and purposes LibSass is fully compatible with the Sass language spec. Any known +differences can be found as open issues. + + + About Sass ---------- @@ -87,7 +88,7 @@ Sass was originally conceived of by the co-creator of this library, Hampton Catl Most of the language has been the result of years of work by Natalie Weizenbaum ([@nex3]) and Chris Eppstein ([@chriseppstein]). -For more information about Sass itself, please visit http://sass-lang.com +For more information about Sass itself, please visit https://sass-lang.com Initial development of LibSass by Aaron Leung and Hampton Catlin was supported by [Moovweb](http://www.moovweb.com). diff --git a/src/libsass/appveyor.yml b/src/libsass/appveyor.yml index d964fade4..f46220784 100644 --- a/src/libsass/appveyor.yml +++ b/src/libsass/appveyor.yml @@ -2,7 +2,7 @@ os: Visual Studio 2013 environment: CTEST_OUTPUT_ON_FAILURE: 1 - ruby_version: 22-x64 + ruby_version: 24-x64 TargetPath: sassc/bin/sassc.exe matrix: - Compiler: msvc @@ -23,12 +23,21 @@ cache: - C:\Ruby%ruby_version%\lib\ruby\gems - C:\mingw64 +# Uncomment to debug hanging builds via RDP, password can be found/set under Environment-Variables in appveyor settings! +# +# init: +# - ps: iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1')) +# +# on_finish: +# - ps: $blockRdp = $true; iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1')) + install: - git clone https://github.com/sass/sassc.git - git clone https://github.com/sass/sass-spec.git - set PATH=C:\Ruby%ruby_version%\bin;%PATH% - ps: | if(!(gem which minitest 2>$nul)) { gem install minitest --no-ri --no-rdoc } + if(!(gem which hrx 2>$nul)) { gem install hrx --no-ri --no-rdoc } if ($env:Compiler -eq "mingw" -AND -Not (Test-Path "C:\mingw64")) { # Install MinGW. $file = "x86_64-4.9.2-release-win32-seh-rt_v4-rev3.7z" @@ -70,7 +79,7 @@ test_script: } $env:TargetPath = Join-Path $pwd.Path $env:TargetPath If (Test-Path "$env:TargetPath") { - ruby sass-spec/sass-spec.rb -V 3.5 --probe-todo --impl libsass -c $env:TargetPath -s sass-spec/spec + ruby sass-spec/sass-spec.rb --probe-todo --impl libsass -c $env:TargetPath -s sass-spec/spec if(-not($?)) { echo "sass-spec tests failed" exit 1 @@ -79,9 +88,9 @@ test_script: echo "spec runner not found (compile error?)" exit 1 } - Write-Host "Explicitly testing the case when cwd has Cyrillic characters: " -nonewline + Write-Host "Explicitly testing the case when cwd has Unicode characters: " -nonewline # See comments in gh-1774 for details. - cd sass-spec/spec/libsass/Sáss-UŢF8/ + cd test/e2e/unicode-pwd/Sáss-UŢF8/ &$env:TargetPath ./input.scss 2>&1>$null if(-not($?)) { echo "Failed!" diff --git a/src/libsass/docs/README.md b/src/libsass/docs/README.md index a233fae48..c0d4491e2 100644 --- a/src/libsass/docs/README.md +++ b/src/libsass/docs/README.md @@ -15,6 +15,6 @@ Or make your own! | Issue Tracker | Issue Triage | Community Guidelines | |-------------------|----------------------------------|-----------------------------| -| We're always needing help, so check out our issue tracker, help some people out, and read our article on [Contributing](contributing.md)! It's got all the details on what to do! | To help understand the process of triaging bugs, have a look at our [Issue-Triage](triage.md) document. | Oh, and don't forget we always follow [[Sass Community Guidelines|http://sass-lang.com/community-guidelines]]. Be nice and everyone else will be nice too! | +| We're always needing help, so check out our issue tracker, help some people out, and read our article on [Contributing](contributing.md)! It's got all the details on what to do! | To help understand the process of triaging bugs, have a look at our [Issue-Triage](triage.md) document. | Oh, and don't forget we always follow [Sass Community Guidelines](https://sass-lang.com/community-guidelines). Be nice and everyone else will be nice too! | Please refer to the steps on [Building LibSass](build.md) diff --git a/src/libsass/docs/api-context-example.md b/src/libsass/docs/api-context-example.md index 4f2a2a0ce..97585a8b3 100644 --- a/src/libsass/docs/api-context-example.md +++ b/src/libsass/docs/api-context-example.md @@ -1,6 +1,55 @@ -## Example main.c +## Example for `data_context` -```C +```C:data.c +#include +#include "sass/context.h" + +int main( int argc, const char* argv[] ) +{ + + // LibSass will take control of data you pass in + // Therefore we need to make a copy of static data + char* text = sass_copy_c_string("a{b:c;}"); + // Normally you'll load data into a buffer from i.e. the disk. + // Use `sass_alloc_memory` to get a buffer to pass to LibSass + // then fill it with data you load from disk or somewhere else. + + // create the data context and get all related structs + struct Sass_Data_Context* data_ctx = sass_make_data_context(text); + struct Sass_Context* ctx = sass_data_context_get_context(data_ctx); + struct Sass_Options* ctx_opt = sass_context_get_options(ctx); + + // configure some options ... + sass_option_set_precision(ctx_opt, 10); + + // context is set up, call the compile step now + int status = sass_compile_data_context(data_ctx); + + // print the result or the error to the stdout + if (status == 0) puts(sass_context_get_output_string(ctx)); + else puts(sass_context_get_error_message(ctx)); + + // release allocated memory + sass_delete_data_context(data_ctx); + + // exit status + return status; + +} +``` + +### Compile data.c + +```bash +gcc -c data.c -o data.o +gcc -o sample data.o -lsass +echo "foo { margin: 21px * 2; }" > foo.scss +./sample foo.scss => "foo { margin: 42px }" +``` + +## Example for `file_context` + +```C:file.c #include #include "sass/context.h" @@ -34,11 +83,11 @@ int main( int argc, const char* argv[] ) } ``` -### Compile main.c +### Compile file.c ```bash -gcc -c main.c -o main.o -gcc -o sample main.o -lsass +gcc -c file.c -o file.o +gcc -o sample file.o -lsass echo "foo { margin: 21px * 2; }" > foo.scss ./sample foo.scss => "foo { margin: 42px }" ``` diff --git a/src/libsass/docs/api-context-internal.md b/src/libsass/docs/api-context-internal.md index 1a2818b34..901b277c1 100644 --- a/src/libsass/docs/api-context-internal.md +++ b/src/libsass/docs/api-context-internal.md @@ -117,7 +117,7 @@ struct Sass_Context : Sass_Options char* error_file; size_t error_line; size_t error_column; - const char* error_src; + char* error_src; // report imported files char** included_files; diff --git a/src/libsass/docs/api-context.md b/src/libsass/docs/api-context.md index dfd10c181..e101da308 100644 --- a/src/libsass/docs/api-context.md +++ b/src/libsass/docs/api-context.md @@ -124,6 +124,7 @@ char* error_message; char* error_file; size_t error_line; size_t error_column; +char* error_src; ``` ```C // report imported files @@ -171,7 +172,7 @@ struct Sass_Compiler* sass_make_file_compiler (struct Sass_File_Context* file_ct struct Sass_Compiler* sass_make_data_compiler (struct Sass_Data_Context* data_ctx); // Execute the different compilation steps individually -// Usefull if you only want to query the included files +// Useful if you only want to query the included files int sass_compiler_parse (struct Sass_Compiler* compiler); int sass_compiler_execute (struct Sass_Compiler* compiler); @@ -202,6 +203,7 @@ const char* sass_context_get_error_json (struct Sass_Context* ctx); const char* sass_context_get_error_text (struct Sass_Context* ctx); const char* sass_context_get_error_message (struct Sass_Context* ctx); const char* sass_context_get_error_file (struct Sass_Context* ctx); +const char* sass_context_get_error_src (struct Sass_Context* ctx); size_t sass_context_get_error_line (struct Sass_Context* ctx); size_t sass_context_get_error_column (struct Sass_Context* ctx); const char* sass_context_get_source_map_string (struct Sass_Context* ctx); @@ -221,6 +223,7 @@ char* sass_context_take_error_json (struct Sass_Context* ctx); char* sass_context_take_error_text (struct Sass_Context* ctx); char* sass_context_take_error_message (struct Sass_Context* ctx); char* sass_context_take_error_file (struct Sass_Context* ctx); +char* sass_context_take_error_src (struct Sass_Context* ctx); char* sass_context_take_output_string (struct Sass_Context* ctx); char* sass_context_take_source_map_string (struct Sass_Context* ctx); ``` diff --git a/src/libsass/docs/api-doc.md b/src/libsass/docs/api-doc.md index 376561612..3a596e8d5 100644 --- a/src/libsass/docs/api-doc.md +++ b/src/libsass/docs/api-doc.md @@ -51,6 +51,10 @@ process. The compiler has two different modes: direct input as a string with `Sass_File_Context`. See the code for a list of options available [Sass_Options](https://github.com/sass/libsass/blob/36feef0/include/sass/interface.h#L18) +The general rule is if the API takes `const char*` it will make a copy, +but where the API is `char*` it will take over memory ownership, so make sure to pass +in memory that is allocated via `sass_copy_c_string` or `sass_alloc_memory`. + **Building a file compiler** context = sass_make_file_context("file.scss") @@ -73,7 +77,11 @@ process. The compiler has two different modes: direct input as a string with **Building a data compiler** - context = sass_make_data_context("div { a { color: blue; } }") + // LibSass takes over memory owenership, make sure to allocate + // a buffer via `sass_alloc_memory` or `sass_copy_c_string`. + buffer = sass_copy_c_string("div { a { color: blue; } }") + + context = sass_make_data_context(buffer) options = sass_data_context_get_options(context) sass_option_set_precision(options, 1) sass_option_set_source_comments(options, true) @@ -121,6 +129,7 @@ is not destroyed (`sass_delete_context`). LibSass will create copies of most inputs/options beside the main sass code. You need to allocate and fill that buffer before passing it to LibSass. You may also overtake memory management from libsass for certain return values (i.e. `sass_context_take_output_string`). +Make sure to free it via `sass_free_memory`. ```C // to allocate buffer to be filled @@ -152,7 +161,7 @@ const char* libsass_language_version(void); The `input_path` is part of `Sass_Options`, but it also is the main option for `Sass_File_Context`. It is also used to generate relative file links in source- -maps. Therefore it is pretty usefull to pass this information if you have a +maps. Therefore it is pretty useful to pass this information if you have a `Sass_Data_Context` and know the original path. **output_path** @@ -186,7 +195,7 @@ The proof is in the pudding, so we have highlighted a few implementations that should be on par with the latest LibSass interface version. Some of them may not have all features implemented! -1. [Perl Example](https://github.com/sass/perl-libsass/blob/master/lib/CSS/Sass.xs) +1. [Perl Example](https://github.com/sass/perl-libsass/blob/master/Sass.xs) 2. [Go Example](https://godoc.org/github.com/wellington/go-libsass#example-Compiler--Stdin) 3. [Node Example](https://github.com/sass/node-sass/blob/master/src/binding.cpp) diff --git a/src/libsass/docs/api-value-example.md b/src/libsass/docs/api-value-example.md index 690654eaf..2d332110a 100644 --- a/src/libsass/docs/api-value-example.md +++ b/src/libsass/docs/api-value-example.md @@ -31,7 +31,7 @@ int main( int argc, const char* argv[] ) sass_delete_value(total); // print the result - you may want to make - // sure result is indeed a string, altough + // sure result is indeed a string, although // stringify guarantees to return a string // if (sass_value_is_string(result)) {} // really depends on your level of paranoia diff --git a/src/libsass/docs/build-on-windows.md b/src/libsass/docs/build-on-windows.md index 0afaa2e4c..458131909 100644 --- a/src/libsass/docs/build-on-windows.md +++ b/src/libsass/docs/build-on-windows.md @@ -130,7 +130,7 @@ cd libsass REM set PATH=%PATH%;%PROGRAMFILES%\MSBuild\12.0\Bin msbuild /m:4 /p:Configuration=Release win\libsass.sln REM running the spec test-suite manually (needs ruby and minitest gem) -ruby sass-spec\sass-spec.rb -V 3.5 -c win\bin\sassc.exe -s --impl libsass sass-spec/spec +ruby sass-spec\sass-spec.rb -c win\bin\sassc.exe -s --impl libsass sass-spec/spec cd .. ``` diff --git a/src/libsass/docs/compatibility-plan.md b/src/libsass/docs/compatibility-plan.md index d8e538fa4..3ece109a9 100644 --- a/src/libsass/docs/compatibility-plan.md +++ b/src/libsass/docs/compatibility-plan.md @@ -1,6 +1,6 @@ This document is to serve as a living, changing plan for getting LibSass caught up with Ruby Sass. -_Note: an "s" preceeding a version number is specifying a Ruby Sass version. Without an s, it's a version of LibSass._ +_Note: an "s" preceding a version number is specifying a Ruby Sass version. Without an s, it's a version of LibSass._ # Goal **Our goal is to reach full s3.4 compatibility as soon as possible. LibSass version 3.4 will behave just like Ruby Sass 3.4** diff --git a/src/libsass/docs/custom-functions-internal.md b/src/libsass/docs/custom-functions-internal.md index 57fec82b8..86784333b 100644 --- a/src/libsass/docs/custom-functions-internal.md +++ b/src/libsass/docs/custom-functions-internal.md @@ -39,7 +39,7 @@ union Sass_Value* call_sass_function( ### cookie -The cookie can hold any pointer you want. In the `perl-libsass` implementation it holds the structure with the reference of the actual registered callback into the perl interpreter. Before that call `perl-libsass` will convert all `Sass_Values` to corresponding perl data types (so they can be used natively inside the perl interpretor). The callback can also return a `Sass_Value`. In `perl-libsass` the actual function returns a perl value, which has to be converted before `libsass` can work with it again! +The cookie can hold any pointer you want. In the `perl-libsass` implementation it holds the structure with the reference of the actual registered callback into the perl interpreter. Before that call `perl-libsass` will convert all `Sass_Values` to corresponding perl data types (so they can be used natively inside the perl interpreter). The callback can also return a `Sass_Value`. In `perl-libsass` the actual function returns a perl value, which has to be converted before `libsass` can work with it again! ## Sass_Values diff --git a/src/libsass/docs/dev-ast-memory.md b/src/libsass/docs/dev-ast-memory.md index 31004bcf2..4f2647553 100644 --- a/src/libsass/docs/dev-ast-memory.md +++ b/src/libsass/docs/dev-ast-memory.md @@ -50,7 +50,7 @@ I often use the terminology of "pickup". This means the moment when a raw pointer not under any control is assigned to a reference counted object (`XYZ_Obj = XYZ_Ptr`). From that point on memory will be automatically released once the object goes out of scope (but only -if the reference counter reaches zero). Main point beeing, you don't +if the reference counter reaches zero). Main point being, you don't have to worry about memory management yourself. ### Object detach @@ -212,7 +212,7 @@ profound advantages: - Better GCC 4.4 compatibility (which most code still has OOTB) - Not thread safe (give us some free performance on some compiler) -- Beeing able to track memory allocations for debugging purposes +- Being able to track memory allocations for debugging purposes - Adding additional features if needed (as seen in `detach`) - Optional: optimized weak pointer implementation possible diff --git a/src/libsass/docs/dev-profiling.md b/src/libsass/docs/dev-profiling.md new file mode 100644 index 000000000..8c8c6035a --- /dev/null +++ b/src/libsass/docs/dev-profiling.md @@ -0,0 +1,73 @@ +# Profiling LibSass + +## Linux perf and pprof + +On Linux, you can record the profile with `perf` and inspect it with `pprof`. + +### Install required tools + +Pre-requisites: + +1. Linux `perf`, commonly found in the `linux-tools-generic` package. +2. [go], for installing `pprof`. +3. [bazel], for installing `perf_to_profile`. + +[go]: https://golang.org +[bazel]: https://bazel.build + +First, install `pprof` with: + +```bash +go get -u github.com/google/pprof +``` + +Then, build and install `perf_to_profile`: + +```bash +git clone https://github.com/google/perf_data_converter +cd perf_data_converter +bazel build -c opt src:perf_to_profile +sudo cp bazel-bin/src/perf_to_profile /usr/local/bin/ +``` + +Finally, in your libsass repository, clone and build `sassc`: + +```bash +git clone https://github.com/sass/sassc.git +make sassc +``` + +### Record perf data + +```bash +sudo perf record sassc/bin/sassc input.scss > /dev/null && sudo chown $USER:$USER perf.data +``` + +This will create a `perf.data` file that you can vizualize with `pprof`. + +### Inspect perf data + +A web server with various visualization options: + +```bash +pprof -http=localhost:3232 sassc/bin/sassc perf.data +``` + +Simple text output: + +```bash +pprof -text sassc/bin/sassc perf.data +``` + +Example output: + +``` + flat flat% sum% cum cum% + 24651348 6.97% 6.97% 24651348 6.97% [[kernel.kallsyms]] + 20746241 5.87% 12.84% 20746241 5.87% Sass::SharedPtr::decRefCount + 18401663 5.20% 18.04% 20420896 5.78% __libc_malloc + 15205959 4.30% 22.34% 15205959 4.30% [libc-2.27.so] + 12974307 3.67% 26.01% 14070189 3.98% _int_malloc + 10958857 3.10% 29.11% 10958857 3.10% Sass::SharedPtr::incRefCount + 9837672 2.78% 31.89% 18433250 5.21% cfree +``` diff --git a/src/libsass/docs/implementations.md b/src/libsass/docs/implementations.md index 5239adcde..942e2de02 100644 --- a/src/libsass/docs/implementations.md +++ b/src/libsass/docs/implementations.md @@ -36,6 +36,8 @@ There are several implementations of `libsass` for a variety of languages. Here * [LibSassHost](https://github.com/Taritsyn/LibSassHost) ### Nim + +* [sass](https://github.com/dom96/sass) * [nim-sass](https://github.com/zacharycarter/nim-sass) ### node.js diff --git a/src/libsass/docs/setup-environment.md b/src/libsass/docs/setup-environment.md index 805613656..e30845b6e 100644 --- a/src/libsass/docs/setup-environment.md +++ b/src/libsass/docs/setup-environment.md @@ -66,3 +66,6 @@ bundle install Voila! Now you are testing against Sass too! +## Profiling + +Is libsass being slow? See the [Profiling guide](dev-profiling.md). diff --git a/src/libsass/docs/source-map-internals.md b/src/libsass/docs/source-map-internals.md index 50f83b54f..fd829a198 100644 --- a/src/libsass/docs/source-map-internals.md +++ b/src/libsass/docs/source-map-internals.md @@ -28,7 +28,7 @@ if (lex< custom_property_name >()) { ## How is the `source_position` calculated -This is automatically done with `lex` in `parser.hpp`. Whenever something is lexed, the `source_position` is updated. But be aware that `source_position` points to the begining of the parsed text. If you need a mapping for the position where the parsing ended, you need to add another call to `lex` (to match nothing)! +This is automatically done with `lex` in `parser.hpp`. Whenever something is lexed, the `source_position` is updated. But be aware that `source_position` points to the beginning of the parsed text. If you need a mapping for the position where the parsing ended, you need to add another call to `lex` (to match nothing)! ``` lex< exactly < empty_str > >(); diff --git a/src/libsass/docs/unicode.md b/src/libsass/docs/unicode.md index a1eb5b1cf..309d70e44 100644 --- a/src/libsass/docs/unicode.md +++ b/src/libsass/docs/unicode.md @@ -36,7 +36,7 @@ LibSass currently reads all kind of BOMs and will error out if it finds somethin ### Low priority feature I guess the current implementation should handle more than 99% of all real world use cases. -A) Unicode characters are still seldomly seen (as they can be written escaped) +A) Unicode characters are still seldom seen (as they can be written escaped) ~~B) It will still work if it's UTF-8 or in any of the most common known western ISO codepages. Although I'm not sure how this applies to asian and other "exotic" codepages!~~ diff --git a/src/libsass/extconf.rb b/src/libsass/extconf.rb deleted file mode 100644 index 3e6d00bc9..000000000 --- a/src/libsass/extconf.rb +++ /dev/null @@ -1,6 +0,0 @@ -require 'mkmf' -# .. more stuff -#$LIBPATH.push(Config::CONFIG['libdir']) -$CFLAGS << " #{ENV["CFLAGS"]}" -$LIBS << " #{ENV["LIBS"]}" -create_makefile("libsass") diff --git a/src/libsass/include/sass/base.h b/src/libsass/include/sass/base.h index 88dd8d303..132da693a 100644 --- a/src/libsass/include/sass/base.h +++ b/src/libsass/include/sass/base.h @@ -1,6 +1,7 @@ #ifndef SASS_BASE_H #define SASS_BASE_H +// #define DEBUG // #define DEBUG_SHARED_PTR #ifdef _MSC_VER @@ -16,6 +17,12 @@ #endif #endif +// Work around lack of `noexcept` keyword support in VS2013 +#if defined(_MSC_VER) && (_MSC_VER <= 1800) && !defined(_ALLOW_KEYWORD_MACROS) +#define _ALLOW_KEYWORD_MACROS 1 +#define noexcept throw( ) +#endif + #include #include @@ -61,7 +68,8 @@ enum Sass_Output_Style { SASS_STYLE_COMPRESSED, // only used internaly SASS_STYLE_INSPECT, - SASS_STYLE_TO_SASS + SASS_STYLE_TO_SASS, + SASS_STYLE_TO_CSS }; // to allocate buffer to be filled diff --git a/src/libsass/include/sass/context.h b/src/libsass/include/sass/context.h index 2f88d6888..68f640ce3 100644 --- a/src/libsass/include/sass/context.h +++ b/src/libsass/include/sass/context.h @@ -43,7 +43,7 @@ ADDAPI struct Sass_Compiler* ADDCALL sass_make_file_compiler (struct Sass_File_C ADDAPI struct Sass_Compiler* ADDCALL sass_make_data_compiler (struct Sass_Data_Context* data_ctx); // Execute the different compilation steps individually -// Usefull if you only want to query the included files +// 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); @@ -134,6 +134,7 @@ 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); diff --git a/src/libsass/script/ci-build-libsass b/src/libsass/script/ci-build-libsass index 40ea22ff7..d4ade4eb7 100755 --- a/src/libsass/script/ci-build-libsass +++ b/src/libsass/script/ci-build-libsass @@ -85,13 +85,14 @@ if [ "x$AUTOTOOLS" == "xyes" ]; then make $MAKE_OPTS clean - # install to prefix directory - PREFIX="$PREFIX" make $MAKE_OPTS install - else make $MAKE_OPTS clean + # Run C++ unit tests + make -C test clean + make -C test test + fi # install to prefix directory @@ -125,10 +126,10 @@ then 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 - LD_LIBRARY_PATH="$PREFIX/lib/" make $MAKE_OPTS test_probe + make $MAKE_OPTS test_probe else - LD_LIBRARY_PATH="$PREFIX/lib/" make $MAKE_OPTS test_probe + make $MAKE_OPTS test_probe fi else - LD_LIBRARY_PATH="$PREFIX/lib/" make $MAKE_OPTS test_probe + make $MAKE_OPTS test_probe fi diff --git a/src/libsass/script/ci-build-plugin b/src/libsass/script/ci-build-plugin index 0dd67b991..dc2faf4d2 100755 --- a/src/libsass/script/ci-build-plugin +++ b/src/libsass/script/ci-build-plugin @@ -50,8 +50,8 @@ cd ../../.. # glob only works on paths relative to imports if [ "x$PLUGIN" == "xglob" ]; then - ${SASSC_BIN} --plugin-path plugins/libsass-${PLUGIN}/build ${SASS_SPEC_SPEC_DIR}/basic/input.scss > ${SASS_SPEC_SPEC_DIR}/basic/result.css - ${SASSC_BIN} --plugin-path plugins/libsass-${PLUGIN}/build ${SASS_SPEC_SPEC_DIR}/basic/input.scss --sourcemap > /dev/null + ${SASSC_BIN} --precision 5 --plugin-path plugins/libsass-${PLUGIN}/build ${SASS_SPEC_SPEC_DIR}/basic/input.scss > ${SASS_SPEC_SPEC_DIR}/basic/result.css + ${SASSC_BIN} --precision 5 --plugin-path plugins/libsass-${PLUGIN}/build ${SASS_SPEC_SPEC_DIR}/basic/input.scss --sourcemap > /dev/null else cat ${SASS_SPEC_SPEC_DIR}/basic/input.scss | ${SASSC_BIN} --precision 5 --plugin-path plugins/libsass-${PLUGIN}/build -I ${SASS_SPEC_SPEC_DIR}/basic > ${SASS_SPEC_SPEC_DIR}/basic/result.css cat ${SASS_SPEC_SPEC_DIR}/basic/input.scss | ${SASSC_BIN} --precision 5 --plugin-path plugins/libsass-${PLUGIN}/build -I ${SASS_SPEC_SPEC_DIR}/basic --sourcemap > /dev/null diff --git a/src/libsass/script/ci-install-compiler b/src/libsass/script/ci-install-compiler index 3a68b3a39..0727d7232 100755 --- a/src/libsass/script/ci-install-compiler +++ b/src/libsass/script/ci-install-compiler @@ -2,5 +2,6 @@ gem install minitest gem install minitap +gem install hrx pip2 install --user 'requests[security]' diff --git a/src/libsass/src/GNUmakefile.am b/src/libsass/src/GNUmakefile.am index fee9312f9..9b0e6a99b 100644 --- a/src/libsass/src/GNUmakefile.am +++ b/src/libsass/src/GNUmakefile.am @@ -13,11 +13,7 @@ AM_CFLAGS = $(AM_COPT) AM_CXXFLAGS = $(AM_COPT) AM_LDFLAGS = $(AM_COPT) $(AM_COVLDFLAGS) -if COMPILER_IS_MINGW32 - AM_CXXFLAGS += -std=gnu++0x -else - AM_CXXFLAGS += -std=c++0x -endif +AM_CXXFLAGS += -std=c++11 EXTRA_DIST = \ COPYING \ diff --git a/src/libsass/src/ast.cpp b/src/libsass/src/ast.cpp index c3b38efb9..bf2ea961a 100644 --- a/src/libsass/src/ast.cpp +++ b/src/libsass/src/ast.cpp @@ -1,1743 +1,681 @@ +// sass.hpp must go before all system headers to get the +// __EXTENSIONS__ fix on Solaris. #include "sass.hpp" -#include "ast.hpp" -#include "context.hpp" -#include "node.hpp" -#include "eval.hpp" -#include "extend.hpp" -#include "emitter.hpp" -#include "color_maps.hpp" -#include "ast_fwd_decl.hpp" -#include -#include -#include -#include -#include -#include -#include - -namespace Sass { - - static Null sass_null(ParserState("null")); - - bool Wrapped_Selector::find ( bool (*f)(AST_Node_Obj) ) - { - // check children first - if (selector_) { - if (selector_->find(f)) return true; - } - // execute last - return f(this); - } - - bool Selector_List::find ( bool (*f)(AST_Node_Obj) ) - { - // check children first - for (Complex_Selector_Obj sel : elements()) { - if (sel->find(f)) return true; - } - // execute last - return f(this); - } - - bool Compound_Selector::find ( bool (*f)(AST_Node_Obj) ) - { - // check children first - for (Simple_Selector_Obj sel : elements()) { - if (sel->find(f)) return true; - } - // execute last - return f(this); - } - - bool Complex_Selector::find ( bool (*f)(AST_Node_Obj) ) - { - // check children first - if (head_ && head_->find(f)) return true; - if (tail_ && tail_->find(f)) return true; - // execute last - return f(this); - } - - bool Supports_Operator::needs_parens(Supports_Condition_Obj cond) const { - if (Supports_Operator_Obj op = Cast(cond)) { - return op->operand() != operand(); - } - return Cast(cond) != NULL; - } - - bool Supports_Negation::needs_parens(Supports_Condition_Obj cond) const { - return Cast(cond) || - Cast(cond); - } - - void str_rtrim(std::string& str, const std::string& delimiters = " \f\n\r\t\v") - { - str.erase( str.find_last_not_of( delimiters ) + 1 ); - } - - void String_Constant::rtrim() - { - str_rtrim(value_); - } - - void String_Schema::rtrim() - { - if (!empty()) { - if (String_Ptr str = Cast(last())) str->rtrim(); - } - } - - void Argument::set_delayed(bool delayed) - { - if (value_) value_->set_delayed(delayed); - is_delayed(delayed); - } - - void Arguments::set_delayed(bool delayed) - { - for (Argument_Obj arg : elements()) { - if (arg) arg->set_delayed(delayed); - } - is_delayed(delayed); - } - - - bool At_Root_Query::exclude(std::string str) - { - bool with = feature() && unquote(feature()->to_string()).compare("with") == 0; - List_Ptr l = static_cast(value().ptr()); - std::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; - } - } - - void AST_Node::update_pstate(const ParserState& pstate) - { - pstate_.offset += pstate - pstate_ + pstate.offset; - } - bool Simple_Selector::is_ns_eq(const Simple_Selector& r) const - { - // https://github.com/sass/sass/issues/2229 - if ((has_ns_ == r.has_ns_) || - (has_ns_ && ns_.empty()) || - (r.has_ns_ && r.ns_.empty()) - ) { - if (ns_.empty() && r.ns() == "*") return false; - else if (r.ns().empty() && ns() == "*") return false; - else return ns() == r.ns(); - } - return false; - } - - bool Compound_Selector::operator< (const Compound_Selector& rhs) const - { - size_t L = std::min(length(), rhs.length()); - for (size_t i = 0; i < L; ++i) - { - Simple_Selector_Obj l = (*this)[i]; - Simple_Selector_Obj r = rhs[i]; - if (!l && !r) return false; - else if (!r) return false; - else if (!l) return true; - else if (*l != *r) - { return *l < *r; } - } - // just compare the length now - return length() < rhs.length(); - } - - bool Compound_Selector::has_parent_ref() const - { - for (Simple_Selector_Obj s : *this) { - if (s && s->has_parent_ref()) return true; - } - return false; - } - - bool Compound_Selector::has_real_parent_ref() const - { - for (Simple_Selector_Obj s : *this) { - if (s && s->has_real_parent_ref()) return true; - } - return false; - } - - bool Complex_Selector::has_parent_ref() const - { - return (head() && head()->has_parent_ref()) || - (tail() && tail()->has_parent_ref()); - } - - bool Complex_Selector::has_real_parent_ref() const - { - return (head() && head()->has_real_parent_ref()) || - (tail() && tail()->has_real_parent_ref()); - } - - bool Complex_Selector::operator< (const Complex_Selector& rhs) const - { - // const iterators for tails - Complex_Selector_Ptr_Const l = this; - Complex_Selector_Ptr_Const r = &rhs; - Compound_Selector_Ptr l_h = NULL; - Compound_Selector_Ptr r_h = NULL; - if (l) l_h = l->head(); - if (r) r_h = r->head(); - // process all tails - while (true) - { - #ifdef DEBUG - // skip empty ancestor first - if (l && l->is_empty_ancestor()) - { - l_h = NULL; - l = l->tail(); - if(l) l_h = l->head(); - continue; - } - // skip empty ancestor first - if (r && r->is_empty_ancestor()) - { - r_h = NULL; - r = r->tail(); - if (r) r_h = r->head(); - continue; - } - #endif - // check for valid selectors - if (!l) return !!r; - if (!r) return false; - // both are null - else if (!l_h && !r_h) - { - // check combinator after heads - if (l->combinator() != r->combinator()) - { return l->combinator() < r->combinator(); } - // advance to next tails - l = l->tail(); - r = r->tail(); - // fetch the next headers - l_h = NULL; r_h = NULL; - if (l) l_h = l->head(); - if (r) r_h = r->head(); - } - // one side is null - else if (!r_h) return true; - else if (!l_h) return false; - // heads ok and equal - else if (*l_h == *r_h) - { - // check combinator after heads - if (l->combinator() != r->combinator()) - { return l->combinator() < r->combinator(); } - // advance to next tails - l = l->tail(); - r = r->tail(); - // fetch the next headers - l_h = NULL; r_h = NULL; - if (l) l_h = l->head(); - if (r) r_h = r->head(); - } - // heads are not equal - else return *l_h < *r_h; - } - } - - bool Complex_Selector::operator== (const Complex_Selector& rhs) const - { - // const iterators for tails - Complex_Selector_Ptr_Const l = this; - Complex_Selector_Ptr_Const r = &rhs; - Compound_Selector_Ptr l_h = NULL; - Compound_Selector_Ptr r_h = NULL; - if (l) l_h = l->head(); - if (r) r_h = r->head(); - // process all tails - while (true) - { - #ifdef DEBUG - // skip empty ancestor first - if (l && l->is_empty_ancestor()) - { - l_h = NULL; - l = l->tail(); - if (l) l_h = l->head(); - continue; - } - // skip empty ancestor first - if (r && r->is_empty_ancestor()) - { - r_h = NULL; - r = r->tail(); - if (r) r_h = r->head(); - continue; - } - #endif - // check the pointers - if (!r) return !l; - if (!l) return !r; - // both are null - if (!l_h && !r_h) - { - // check combinator after heads - if (l->combinator() != r->combinator()) - { return l->combinator() < r->combinator(); } - // advance to next tails - l = l->tail(); - r = r->tail(); - // fetch the next heads - l_h = NULL; r_h = NULL; - if (l) l_h = l->head(); - if (r) r_h = r->head(); - } - // equals if other head is empty - else if ((!l_h && !r_h) || - (!l_h && r_h->empty()) || - (!r_h && l_h->empty()) || - (l_h && r_h && *l_h == *r_h)) - { - // check combinator after heads - if (l->combinator() != r->combinator()) - { return l->combinator() == r->combinator(); } - // advance to next tails - l = l->tail(); - r = r->tail(); - // fetch the next heads - l_h = NULL; r_h = NULL; - if (l) l_h = l->head(); - if (r) r_h = r->head(); - } - // abort - else break; - } - // unreachable - return false; - } - - Compound_Selector_Ptr Compound_Selector::unify_with(Compound_Selector_Ptr rhs) - { - if (empty()) return rhs; - Compound_Selector_Obj unified = SASS_MEMORY_COPY(rhs); - for (size_t i = 0, L = length(); i < L; ++i) - { - if (unified.isNull()) break; - unified = at(i)->unify_with(unified); - } - return unified.detach(); - } - - bool Complex_Selector::operator== (const Selector& rhs) const - { - if (const Selector_List* sl = Cast(&rhs)) return *this == *sl; - if (const Simple_Selector* sp = Cast(&rhs)) return *this == *sp; - if (const Complex_Selector* cs = Cast(&rhs)) return *this == *cs; - if (const Compound_Selector* ch = Cast(&rhs)) return *this == *ch; - throw std::runtime_error("invalid selector base classes to compare"); - } - - - bool Complex_Selector::operator< (const Selector& rhs) const - { - if (const Selector_List* sl = Cast(&rhs)) return *this < *sl; - if (const Simple_Selector* sp = Cast(&rhs)) return *this < *sp; - if (const Complex_Selector* cs = Cast(&rhs)) return *this < *cs; - if (const Compound_Selector* ch = Cast(&rhs)) return *this < *ch; - throw std::runtime_error("invalid selector base classes to compare"); - } - - bool Compound_Selector::operator== (const Selector& rhs) const - { - if (const Selector_List* sl = Cast(&rhs)) return *this == *sl; - if (const Simple_Selector* sp = Cast(&rhs)) return *this == *sp; - if (const Complex_Selector* cs = Cast(&rhs)) return *this == *cs; - if (const Compound_Selector* ch = Cast(&rhs)) return *this == *ch; - throw std::runtime_error("invalid selector base classes to compare"); - } - - bool Compound_Selector::operator< (const Selector& rhs) const - { - if (const Selector_List* sl = Cast(&rhs)) return *this < *sl; - if (const Simple_Selector* sp = Cast(&rhs)) return *this < *sp; - if (const Complex_Selector* cs = Cast(&rhs)) return *this < *cs; - if (const Compound_Selector* ch = Cast(&rhs)) return *this < *ch; - throw std::runtime_error("invalid selector base classes to compare"); - } - - bool Selector_Schema::operator== (const Selector& rhs) const - { - if (const Selector_List* sl = Cast(&rhs)) return *this == *sl; - if (const Simple_Selector* sp = Cast(&rhs)) return *this == *sp; - if (const Complex_Selector* cs = Cast(&rhs)) return *this == *cs; - if (const Compound_Selector* ch = Cast(&rhs)) return *this == *ch; - throw std::runtime_error("invalid selector base classes to compare"); - } - - bool Selector_Schema::operator< (const Selector& rhs) const - { - if (const Selector_List* sl = Cast(&rhs)) return *this < *sl; - if (const Simple_Selector* sp = Cast(&rhs)) return *this < *sp; - if (const Complex_Selector* cs = Cast(&rhs)) return *this < *cs; - if (const Compound_Selector* ch = Cast(&rhs)) return *this < *ch; - throw std::runtime_error("invalid selector base classes to compare"); - } - - bool Simple_Selector::operator== (const Selector& rhs) const - { - if (Simple_Selector_Ptr_Const sp = Cast(&rhs)) return *this == *sp; - return false; - } - - bool Simple_Selector::operator< (const Selector& rhs) const - { - if (Simple_Selector_Ptr_Const sp = Cast(&rhs)) return *this < *sp; - return false; - } - - bool Simple_Selector::operator== (const Simple_Selector& rhs) const - { - // solve the double dispatch problem by using RTTI information via dynamic cast - if (const Pseudo_Selector* lhs = Cast(this)) {return *lhs == rhs; } - else if (const Wrapped_Selector* lhs = Cast(this)) {return *lhs == rhs; } - else if (const Element_Selector* lhs = Cast(this)) {return *lhs == rhs; } - else if (const Attribute_Selector* lhs = Cast(this)) {return *lhs == rhs; } - else if (name_ == rhs.name_) - { return is_ns_eq(rhs); } - else return false; - } - - bool Simple_Selector::operator< (const Simple_Selector& rhs) const - { - // solve the double dispatch problem by using RTTI information via dynamic cast - if (const Pseudo_Selector* lhs = Cast(this)) {return *lhs < rhs; } - else if (const Wrapped_Selector* lhs = Cast(this)) {return *lhs < rhs; } - else if (const Element_Selector* lhs = Cast(this)) {return *lhs < rhs; } - else if (const Attribute_Selector* lhs = Cast(this)) {return *lhs < rhs; } - if (is_ns_eq(rhs)) - { return name_ < rhs.name_; } - return ns_ < rhs.ns_; - } - - bool Selector_List::operator== (const Selector& rhs) const - { - // solve the double dispatch problem by using RTTI information via dynamic cast - if (Selector_List_Ptr_Const sl = Cast(&rhs)) { return *this == *sl; } - else if (Complex_Selector_Ptr_Const cpx = Cast(&rhs)) { return *this == *cpx; } - else if (Compound_Selector_Ptr_Const cpd = Cast(&rhs)) { return *this == *cpd; } - // no compare method - return this == &rhs; - } - - // Selector lists can be compared to comma lists - bool Selector_List::operator== (const Expression& rhs) const - { - // solve the double dispatch problem by using RTTI information via dynamic cast - if (List_Ptr_Const ls = Cast(&rhs)) { return *ls == *this; } - if (Selector_Ptr_Const ls = Cast(&rhs)) { return *this == *ls; } - // compare invalid (maybe we should error?) - return false; - } - - bool Selector_List::operator== (const Selector_List& rhs) const - { - // for array access - size_t i = 0, n = 0; - size_t iL = length(); - size_t nL = rhs.length(); - // create temporary vectors and sort them - std::vector l_lst = this->elements(); - std::vector r_lst = rhs.elements(); - std::sort(l_lst.begin(), l_lst.end(), OrderNodes()); - std::sort(r_lst.begin(), r_lst.end(), OrderNodes()); - // process loop - while (true) - { - // first check for valid index - if (i == iL) return iL == nL; - else if (n == nL) return iL == nL; - // the access the vector items - Complex_Selector_Obj l = l_lst[i]; - Complex_Selector_Obj r = r_lst[n]; - // skip nulls - if (!l) ++i; - else if (!r) ++n; - // do the check - else if (*l != *r) - { return false; } - // advance - ++i; ++n; - } - // there is no break?! - } - - bool Selector_List::operator< (const Selector& rhs) const - { - if (Selector_List_Ptr_Const sp = Cast(&rhs)) return *this < *sp; - return false; - } - - bool Selector_List::operator< (const Selector_List& rhs) const - { - size_t l = rhs.length(); - if (length() < l) l = length(); - for (size_t i = 0; i < l; i ++) { - if (*at(i) < *rhs.at(i)) return true; - } - return false; - } - - Compound_Selector_Ptr Simple_Selector::unify_with(Compound_Selector_Ptr rhs) - { - for (size_t i = 0, L = rhs->length(); i < L; ++i) - { if (to_string() == rhs->at(i)->to_string()) return rhs; } - - // check for pseudo elements because they are always last - size_t i, L; - bool found = false; - if (typeid(*this) == typeid(Pseudo_Selector) || typeid(*this) == typeid(Wrapped_Selector) || typeid(*this) == typeid(Attribute_Selector)) - { - for (i = 0, L = rhs->length(); i < L; ++i) - { - if ((Cast((*rhs)[i]) || Cast((*rhs)[i]) || Cast((*rhs)[i])) && (*rhs)[L-1]->is_pseudo_element()) - { found = true; break; } - } - } - else - { - for (i = 0, L = rhs->length(); i < L; ++i) - { - if (Cast((*rhs)[i]) || Cast((*rhs)[i]) || Cast((*rhs)[i])) - { found = true; break; } - } - } - if (!found) - { - rhs->append(this); - } else { - rhs->elements().insert(rhs->elements().begin() + i, this); - } - return rhs; - } - - Simple_Selector_Ptr Element_Selector::unify_with(Simple_Selector_Ptr rhs) - { - // check if ns can be extended - // true for no ns or universal - if (has_universal_ns()) - { - // but dont extend with universal - // true for valid ns and universal - if (!rhs->is_universal_ns()) - { - // overwrite the name if star is given as name - if (this->name() == "*") { this->name(rhs->name()); } - // now overwrite the namespace name and flag - this->ns(rhs->ns()); this->has_ns(rhs->has_ns()); - // return copy - return this; - } - } - // namespace may changed, check the name now - // overwrite star (but not with another star) - if (name() == "*" && rhs->name() != "*") - { - // simply set the new name - this->name(rhs->name()); - // return copy - return this; - } - // return original - return this; - } - - Compound_Selector_Ptr Element_Selector::unify_with(Compound_Selector_Ptr rhs) - { - // TODO: handle namespaces - - // if the rhs is empty, just return a copy of this - if (rhs->length() == 0) { - rhs->append(this); - return rhs; - } - - Simple_Selector_Ptr rhs_0 = rhs->at(0); - // otherwise, this is a tag name - if (name() == "*") - { - if (typeid(*rhs_0) == typeid(Element_Selector)) - { - // if rhs is universal, just return this tagname + rhs's qualifiers - Element_Selector_Ptr ts = Cast(rhs_0); - rhs->at(0) = this->unify_with(ts); - return rhs; - } - else if (Cast(rhs_0) || Cast(rhs_0)) { - // qualifier is `.class`, so we can prefix with `ns|*.class` - if (has_ns() && !rhs_0->has_ns()) { - if (ns() != "*") rhs->elements().insert(rhs->begin(), this); - } - return rhs; - } - - - return rhs; - } - - if (typeid(*rhs_0) == typeid(Element_Selector)) - { - // if rhs is universal, just return this tagname + rhs's qualifiers - if (rhs_0->name() != "*" && rhs_0->ns() != "*" && rhs_0->name() != name()) return 0; - // otherwise create new compound and unify first simple selector - rhs->at(0) = this->unify_with(rhs_0); - return rhs; - - } - // else it's a tag name and a bunch of qualifiers -- just append them - if (name() != "*") rhs->elements().insert(rhs->begin(), this); - return rhs; - } - - Compound_Selector_Ptr Class_Selector::unify_with(Compound_Selector_Ptr rhs) - { - rhs->has_line_break(has_line_break()); - return Simple_Selector::unify_with(rhs); - } - - Compound_Selector_Ptr Id_Selector::unify_with(Compound_Selector_Ptr rhs) - { - for (size_t i = 0, L = rhs->length(); i < L; ++i) - { - if (Id_Selector_Ptr sel = Cast(rhs->at(i))) { - if (sel->name() != name()) return 0; - } - } - rhs->has_line_break(has_line_break()); - return Simple_Selector::unify_with(rhs); - } - - Compound_Selector_Ptr Pseudo_Selector::unify_with(Compound_Selector_Ptr rhs) - { - if (is_pseudo_element()) - { - for (size_t i = 0, L = rhs->length(); i < L; ++i) - { - if (Pseudo_Selector_Ptr sel = Cast(rhs->at(i))) { - if (sel->is_pseudo_element() && sel->name() != name()) return 0; - } - } - } - return Simple_Selector::unify_with(rhs); - } - - bool Attribute_Selector::operator< (const Attribute_Selector& rhs) const - { - if (is_ns_eq(rhs)) { - if (name() == rhs.name()) { - if (matcher() == rhs.matcher()) { - bool no_lhs_val = value().isNull(); - bool no_rhs_val = rhs.value().isNull(); - if (no_lhs_val && no_rhs_val) return false; // equal - else if (no_lhs_val) return true; // lhs is null - else if (no_rhs_val) return false; // rhs is null - return *value() < *rhs.value(); // both are given - } else { return matcher() < rhs.matcher(); } - } else { return name() < rhs.name(); } - } else { return ns() < rhs.ns(); } - } - - bool Attribute_Selector::operator< (const Simple_Selector& rhs) const - { - if (Attribute_Selector_Ptr_Const w = Cast(&rhs)) - { - return *this < *w; - } - if (is_ns_eq(rhs)) - { return name() < rhs.name(); } - return ns() < rhs.ns(); - } - - bool Attribute_Selector::operator== (const Attribute_Selector& rhs) const - { - // get optional value state - bool no_lhs_val = value().isNull(); - bool no_rhs_val = rhs.value().isNull(); - // both are null, therefore equal - if (no_lhs_val && no_rhs_val) { - return (name() == rhs.name()) - && (matcher() == rhs.matcher()) - && (is_ns_eq(rhs)); - } - // both are defined, evaluate - if (no_lhs_val == no_rhs_val) { - return (name() == rhs.name()) - && (matcher() == rhs.matcher()) - && (is_ns_eq(rhs)) - && (*value() == *rhs.value()); - } - // not equal - return false; - - } - - bool Attribute_Selector::operator== (const Simple_Selector& rhs) const - { - if (Attribute_Selector_Ptr_Const w = Cast(&rhs)) - { - return is_ns_eq(rhs) && - name() == rhs.name() && - *this == *w; - } - return false; - } - - bool Element_Selector::operator< (const Element_Selector& rhs) const - { - if (is_ns_eq(rhs)) - { return name() < rhs.name(); } - return ns() < rhs.ns(); - } - - bool Element_Selector::operator< (const Simple_Selector& rhs) const - { - if (Element_Selector_Ptr_Const w = Cast(&rhs)) - { - return *this < *w; - } - if (is_ns_eq(rhs)) - { return name() < rhs.name(); } - return ns() < rhs.ns(); - } - - bool Element_Selector::operator== (const Element_Selector& rhs) const - { - return is_ns_eq(rhs) && - name() == rhs.name(); - } - - bool Element_Selector::operator== (const Simple_Selector& rhs) const - { - if (Element_Selector_Ptr_Const w = Cast(&rhs)) - { - return is_ns_eq(rhs) && - name() == rhs.name() && - *this == *w; - } - return false; - } - - bool Pseudo_Selector::operator== (const Pseudo_Selector& rhs) const - { - if (is_ns_eq(rhs) && name() == rhs.name()) - { - String_Obj lhs_ex = expression(); - String_Obj rhs_ex = rhs.expression(); - if (rhs_ex && lhs_ex) return *lhs_ex == *rhs_ex; - else return lhs_ex.ptr() == rhs_ex.ptr(); - } - else return false; - } - - bool Pseudo_Selector::operator== (const Simple_Selector& rhs) const - { - if (Pseudo_Selector_Ptr_Const w = Cast(&rhs)) - { - return *this == *w; - } - return is_ns_eq(rhs) && - name() == rhs.name(); - } - - bool Pseudo_Selector::operator< (const Pseudo_Selector& rhs) const - { - if (is_ns_eq(rhs) && name() == rhs.name()) - { - String_Obj lhs_ex = expression(); - String_Obj rhs_ex = rhs.expression(); - if (rhs_ex && lhs_ex) return *lhs_ex < *rhs_ex; - else return lhs_ex.ptr() < rhs_ex.ptr(); - } - if (is_ns_eq(rhs)) - { return name() < rhs.name(); } - return ns() < rhs.ns(); - } - - bool Pseudo_Selector::operator< (const Simple_Selector& rhs) const - { - if (Pseudo_Selector_Ptr_Const w = Cast(&rhs)) - { - return *this < *w; - } - if (is_ns_eq(rhs)) - { return name() < rhs.name(); } - return ns() < rhs.ns(); - } - - bool Wrapped_Selector::operator== (const Wrapped_Selector& rhs) const - { - if (is_ns_eq(rhs) && name() == rhs.name()) - { return *(selector()) == *(rhs.selector()); } - else return false; - } - - bool Wrapped_Selector::operator== (const Simple_Selector& rhs) const - { - if (Wrapped_Selector_Ptr_Const w = Cast(&rhs)) - { - return *this == *w; - } - return is_ns_eq(rhs) && - name() == rhs.name(); - } - - bool Wrapped_Selector::operator< (const Wrapped_Selector& rhs) const - { - if (is_ns_eq(rhs) && name() == rhs.name()) - { return *(selector()) < *(rhs.selector()); } - if (is_ns_eq(rhs)) - { return name() < rhs.name(); } - return ns() < rhs.ns(); - } - - bool Wrapped_Selector::operator< (const Simple_Selector& rhs) const - { - if (Wrapped_Selector_Ptr_Const w = Cast(&rhs)) - { - return *this < *w; - } - if (is_ns_eq(rhs)) - { return name() < rhs.name(); } - return ns() < rhs.ns(); - } - - bool Wrapped_Selector::is_superselector_of(Wrapped_Selector_Obj sub) - { - if (this->name() != sub->name()) return false; - if (this->name() == ":current") return false; - if (Selector_List_Obj rhs_list = Cast(sub->selector())) { - if (Selector_List_Obj lhs_list = Cast(selector())) { - return lhs_list->is_superselector_of(rhs_list); - } - } - coreError("is_superselector expected a Selector_List", sub->pstate()); - return false; - } - - bool Compound_Selector::is_superselector_of(Selector_List_Obj rhs, std::string wrapped) - { - for (Complex_Selector_Obj item : rhs->elements()) { - if (is_superselector_of(item, wrapped)) return true; - } - return false; - } - - bool Compound_Selector::is_superselector_of(Complex_Selector_Obj rhs, std::string wrapped) - { - if (rhs->head()) return is_superselector_of(rhs->head(), wrapped); - return false; - } - - bool Compound_Selector::is_superselector_of(Compound_Selector_Obj rhs, std::string wrapping) - { - Compound_Selector_Ptr lhs = this; - Simple_Selector_Ptr lbase = lhs->base(); - Simple_Selector_Ptr rbase = rhs->base(); - - // Check if pseudo-elements are the same between the selectors - - std::set lpsuedoset, rpsuedoset; - for (size_t i = 0, L = length(); i < L; ++i) - { - if ((*this)[i]->is_pseudo_element()) { - std::string pseudo((*this)[i]->to_string()); - pseudo = pseudo.substr(pseudo.find_first_not_of(":")); // strip off colons to ensure :after matches ::after since ruby sass is forgiving - lpsuedoset.insert(pseudo); - } - } - for (size_t i = 0, L = rhs->length(); i < L; ++i) - { - if ((*rhs)[i]->is_pseudo_element()) { - std::string pseudo((*rhs)[i]->to_string()); - pseudo = pseudo.substr(pseudo.find_first_not_of(":")); // strip off colons to ensure :after matches ::after since ruby sass is forgiving - rpsuedoset.insert(pseudo); - } - } - if (lpsuedoset != rpsuedoset) { - return false; - } - - // would like to replace this without stringification - // https://github.com/sass/sass/issues/2229 - // SimpleSelectorSet lset, rset; - std::set lset, rset; - - if (lbase && rbase) - { - if (lbase->to_string() == rbase->to_string()) { - for (size_t i = 1, L = length(); i < L; ++i) - { lset.insert((*this)[i]->to_string()); } - for (size_t i = 1, L = rhs->length(); i < L; ++i) - { rset.insert((*rhs)[i]->to_string()); } - return includes(rset.begin(), rset.end(), lset.begin(), lset.end()); - } - return false; - } - - for (size_t i = 0, iL = length(); i < iL; ++i) - { - Selector_Obj wlhs = (*this)[i]; - // very special case for wrapped matches selector - if (Wrapped_Selector_Obj wrapped = Cast(wlhs)) { - if (wrapped->name() == ":not") { - if (Selector_List_Obj not_list = Cast(wrapped->selector())) { - if (not_list->is_superselector_of(rhs, wrapped->name())) return false; - } else { - throw std::runtime_error("wrapped not selector is not a list"); - } - } - if (wrapped->name() == ":matches" || wrapped->name() == ":-moz-any") { - wlhs = wrapped->selector(); - if (Selector_List_Obj list = Cast(wrapped->selector())) { - if (Compound_Selector_Obj comp = Cast(rhs)) { - if (!wrapping.empty() && wrapping != wrapped->name()) return false; - if (wrapping.empty() || wrapping != wrapped->name()) {; - if (list->is_superselector_of(comp, wrapped->name())) return true; - } - } - } - } - Simple_Selector_Ptr rhs_sel = NULL; - if (rhs->elements().size() > i) rhs_sel = (*rhs)[i]; - if (Wrapped_Selector_Ptr wrapped_r = Cast(rhs_sel)) { - if (wrapped->name() == wrapped_r->name()) { - if (wrapped->is_superselector_of(wrapped_r)) { - continue; - }} - } - } - // match from here on as strings - lset.insert(wlhs->to_string()); - } - - for (size_t n = 0, nL = rhs->length(); n < nL; ++n) - { - Selector_Obj r = (*rhs)[n]; - if (Wrapped_Selector_Obj wrapped = Cast(r)) { - if (wrapped->name() == ":not") { - if (Selector_List_Obj ls = Cast(wrapped->selector())) { - ls->remove_parent_selectors(); - if (is_superselector_of(ls, wrapped->name())) return false; - } - } - if (wrapped->name() == ":matches" || wrapped->name() == ":-moz-any") { - if (!wrapping.empty()) { - if (wrapping != wrapped->name()) return false; - } - if (Selector_List_Obj ls = Cast(wrapped->selector())) { - ls->remove_parent_selectors(); - return (is_superselector_of(ls, wrapped->name())); - } - } - } - rset.insert(r->to_string()); - } - - //for (auto l : lset) { cerr << "l: " << l << endl; } - //for (auto r : rset) { cerr << "r: " << r << endl; } - - if (lset.empty()) return true; - // return true if rset contains all the elements of lset - return includes(rset.begin(), rset.end(), lset.begin(), lset.end()); - - } - - // create complex selector (ancestor of) from compound selector - Complex_Selector_Obj Compound_Selector::to_complex() - { - // create an intermediate complex selector - return SASS_MEMORY_NEW(Complex_Selector, - pstate(), - Complex_Selector::ANCESTOR_OF, - this, - 0); - } - - Selector_List_Ptr Complex_Selector::unify_with(Complex_Selector_Ptr other) - { - - // get last tails (on the right side) - Complex_Selector_Obj l_last = this->last(); - Complex_Selector_Obj r_last = other->last(); - - // check valid pointers (assertion) - SASS_ASSERT(l_last, "lhs is null"); - SASS_ASSERT(r_last, "rhs is null"); - - // Not sure about this check, but closest way I could check - // was to see if this is a ruby 'SimpleSequence' equivalent. - // It seems to do the job correctly as some specs react to this - if (l_last->combinator() != Combinator::ANCESTOR_OF) return 0; - if (r_last->combinator() != Combinator::ANCESTOR_OF ) return 0; - - // get the headers for the last tails - Compound_Selector_Obj l_last_head = l_last->head(); - Compound_Selector_Obj r_last_head = r_last->head(); - - // check valid head pointers (assertion) - SASS_ASSERT(l_last_head, "lhs head is null"); - SASS_ASSERT(r_last_head, "rhs head is null"); - - // get the unification of the last compound selectors - Compound_Selector_Obj unified = r_last_head->unify_with(l_last_head); - - // abort if we could not unify heads - if (unified == 0) return 0; - - // check for universal (star: `*`) selector - bool is_universal = l_last_head->is_universal() || - r_last_head->is_universal(); - - if (is_universal) - { - // move the head - l_last->head(0); - r_last->head(unified); - } - - // create nodes from both selectors - Node lhsNode = complexSelectorToNode(this); - Node rhsNode = complexSelectorToNode(other); - - // overwrite universal base - if (!is_universal) - { - // create some temporaries to convert to node - Complex_Selector_Obj fake = unified->to_complex(); - Node unified_node = complexSelectorToNode(fake); - // add to permutate the list? - rhsNode.plus(unified_node); - } - - // do some magic we inherit from node and extend - Node node = subweave(lhsNode, rhsNode); - Selector_List_Obj result = SASS_MEMORY_NEW(Selector_List, pstate()); - NodeDequePtr col = node.collection(); // move from collection to list - for (NodeDeque::iterator it = col->begin(), end = col->end(); it != end; it++) - { result->append(nodeToComplexSelector(Node::naiveTrim(*it))); } - - // only return if list has some entries - return result->length() ? result.detach() : 0; - - } - - bool Compound_Selector::operator== (const Compound_Selector& rhs) const - { - // for array access - size_t i = 0, n = 0; - size_t iL = length(); - size_t nL = rhs.length(); - // create temporary vectors and sort them - std::vector l_lst = this->elements(); - std::vector r_lst = rhs.elements(); - std::sort(l_lst.begin(), l_lst.end(), OrderNodes()); - std::sort(r_lst.begin(), r_lst.end(), OrderNodes()); - // process loop - while (true) - { - // first check for valid index - if (i == iL) return iL == nL; - else if (n == nL) return iL == nL; - // the access the vector items - Simple_Selector_Obj l = l_lst[i]; - Simple_Selector_Obj r = r_lst[n]; - // skip nulls - if (!l) ++i; - if (!r) ++n; - // do the check now - else if (*l != *r) - { return false; } - // advance now - ++i; ++n; - } - // there is no break?! - } - - bool Complex_Selector::is_superselector_of(Compound_Selector_Obj rhs, std::string wrapping) - { - return last()->head() && last()->head()->is_superselector_of(rhs, wrapping); - } - - bool Complex_Selector::is_superselector_of(Complex_Selector_Obj rhs, std::string wrapping) - { - Complex_Selector_Ptr lhs = this; - // check for selectors with leading or trailing combinators - if (!lhs->head() || !rhs->head()) - { return false; } - Complex_Selector_Obj l_innermost = lhs->innermost(); - if (l_innermost->combinator() != Complex_Selector::ANCESTOR_OF) - { return false; } - Complex_Selector_Obj r_innermost = rhs->innermost(); - if (r_innermost->combinator() != Complex_Selector::ANCESTOR_OF) - { return false; } - // more complex (i.e., longer) selectors are always more specific - size_t l_len = lhs->length(), r_len = rhs->length(); - if (l_len > r_len) - { return false; } - - if (l_len == 1) - { return lhs->head()->is_superselector_of(rhs->last()->head(), wrapping); } - - // we have to look one tail deeper, since we cary the - // combinator around for it (which is important here) - if (rhs->tail() && lhs->tail() && combinator() != Complex_Selector::ANCESTOR_OF) { - Complex_Selector_Obj lhs_tail = lhs->tail(); - Complex_Selector_Obj rhs_tail = rhs->tail(); - if (lhs_tail->combinator() != rhs_tail->combinator()) return false; - if (lhs_tail->head() && !rhs_tail->head()) return false; - if (!lhs_tail->head() && rhs_tail->head()) return false; - if (lhs_tail->head() && rhs_tail->head()) { - if (!lhs_tail->head()->is_superselector_of(rhs_tail->head())) return false; - } - } - - bool found = false; - Complex_Selector_Obj marker = rhs; - for (size_t i = 0, L = rhs->length(); i < L; ++i) { - if (i == L-1) - { return false; } - if (lhs->head() && marker->head() && lhs->head()->is_superselector_of(marker->head(), wrapping)) - { found = true; break; } - marker = marker->tail(); - } - if (!found) - { return false; } - - /* - Hmm, I hope I have the logic right: - - if lhs has a combinator: - if !(marker has a combinator) return false - if !(lhs.combinator == '~' ? marker.combinator != '>' : lhs.combinator == marker.combinator) return false - return lhs.tail-without-innermost.is_superselector_of(marker.tail-without-innermost) - else if marker has a combinator: - if !(marker.combinator == ">") return false - return lhs.tail.is_superselector_of(marker.tail) - else - return lhs.tail.is_superselector_of(marker.tail) - */ - if (lhs->combinator() != Complex_Selector::ANCESTOR_OF) - { - if (marker->combinator() == Complex_Selector::ANCESTOR_OF) - { return false; } - if (!(lhs->combinator() == Complex_Selector::PRECEDES ? marker->combinator() != Complex_Selector::PARENT_OF : lhs->combinator() == marker->combinator())) - { return false; } - return lhs->tail()->is_superselector_of(marker->tail()); - } - else if (marker->combinator() != Complex_Selector::ANCESTOR_OF) - { - if (marker->combinator() != Complex_Selector::PARENT_OF) - { return false; } - return lhs->tail()->is_superselector_of(marker->tail()); - } - return lhs->tail()->is_superselector_of(marker->tail()); - } - - size_t Complex_Selector::length() const - { - // TODO: make this iterative - if (!tail()) return 1; - return 1 + tail()->length(); - } - - // append another complex selector at the end - // check if we need to append some headers - // then we need to check for the combinator - // only then we can safely set the new tail - void Complex_Selector::append(Complex_Selector_Obj ss, Backtraces& traces) - { - - Complex_Selector_Obj t = ss->tail(); - Combinator c = ss->combinator(); - String_Obj r = ss->reference(); - Compound_Selector_Obj h = ss->head(); - - if (ss->has_line_feed()) has_line_feed(true); - if (ss->has_line_break()) has_line_break(true); - - // append old headers - if (h && h->length()) { - if (last()->combinator() != ANCESTOR_OF && c != ANCESTOR_OF) { - traces.push_back(Backtrace(pstate())); - throw Exception::InvalidParent(this, traces, ss); - } else if (last()->head_ && last()->head_->length()) { - Compound_Selector_Obj rh = last()->head(); - size_t i; - size_t L = h->length(); - if (Cast(h->first())) { - if (Class_Selector_Ptr cs = Cast(rh->last())) { - Class_Selector_Ptr sqs = SASS_MEMORY_COPY(cs); - sqs->name(sqs->name() + (*h)[0]->name()); - sqs->pstate((*h)[0]->pstate()); - (*rh)[rh->length()-1] = sqs; - rh->pstate(h->pstate()); - for (i = 1; i < L; ++i) rh->append((*h)[i]); - } else if (Id_Selector_Ptr is = Cast(rh->last())) { - Id_Selector_Ptr sqs = SASS_MEMORY_COPY(is); - sqs->name(sqs->name() + (*h)[0]->name()); - sqs->pstate((*h)[0]->pstate()); - (*rh)[rh->length()-1] = sqs; - rh->pstate(h->pstate()); - for (i = 1; i < L; ++i) rh->append((*h)[i]); - } else if (Element_Selector_Ptr ts = Cast(rh->last())) { - Element_Selector_Ptr tss = SASS_MEMORY_COPY(ts); - tss->name(tss->name() + (*h)[0]->name()); - tss->pstate((*h)[0]->pstate()); - (*rh)[rh->length()-1] = tss; - rh->pstate(h->pstate()); - for (i = 1; i < L; ++i) rh->append((*h)[i]); - } else if (Placeholder_Selector_Ptr ps = Cast(rh->last())) { - Placeholder_Selector_Ptr pss = SASS_MEMORY_COPY(ps); - pss->name(pss->name() + (*h)[0]->name()); - pss->pstate((*h)[0]->pstate()); - (*rh)[rh->length()-1] = pss; - rh->pstate(h->pstate()); - for (i = 1; i < L; ++i) rh->append((*h)[i]); - } else { - last()->head_->concat(h); - } - } else { - last()->head_->concat(h); - } - } else if (last()->head_) { - last()->head_->concat(h); - } - } else { - // std::cerr << "has no or empty head\n"; - } - - if (last()) { - if (last()->combinator() != ANCESTOR_OF && c != ANCESTOR_OF) { - Complex_Selector_Ptr inter = SASS_MEMORY_NEW(Complex_Selector, pstate()); - inter->reference(r); - inter->combinator(c); - inter->tail(t); - last()->tail(inter); - } else { - if (last()->combinator() == ANCESTOR_OF) { - last()->combinator(c); - last()->reference(r); - } - last()->tail(t); - } - } - - } - - Selector_List_Obj Selector_List::eval(Eval& eval) - { - Selector_List_Obj list = schema() ? - eval(schema()) : eval(this); - list->schema(schema()); - return list; - } - - Selector_List_Ptr Selector_List::resolve_parent_refs(std::vector& pstack, Backtraces& traces, bool implicit_parent) - { - if (!this->has_parent_ref()) return this; - Selector_List_Ptr ss = SASS_MEMORY_NEW(Selector_List, pstate()); - Selector_List_Ptr ps = pstack.back(); - for (size_t pi = 0, pL = ps->length(); pi < pL; ++pi) { - for (size_t si = 0, sL = this->length(); si < sL; ++si) { - Selector_List_Obj rv = at(si)->resolve_parent_refs(pstack, traces, implicit_parent); - ss->concat(rv); - } - } - return ss; - } - - Selector_List_Ptr Complex_Selector::resolve_parent_refs(std::vector& pstack, Backtraces& traces, bool implicit_parent) - { - Complex_Selector_Obj tail = this->tail(); - Compound_Selector_Obj head = this->head(); - Selector_List_Ptr parents = pstack.back(); - - if (!this->has_real_parent_ref() && !implicit_parent) { - Selector_List_Ptr retval = SASS_MEMORY_NEW(Selector_List, pstate()); - retval->append(this); - return retval; - } - - // first resolve_parent_refs the tail (which may return an expanded list) - Selector_List_Obj tails = tail ? tail->resolve_parent_refs(pstack, traces, implicit_parent) : 0; - - if (head && head->length() > 0) { - - Selector_List_Obj retval; - // we have a parent selector in a simple compound list - // mix parent complex selector into the compound list - if (Cast((*head)[0])) { - retval = SASS_MEMORY_NEW(Selector_List, pstate()); - - // it turns out that real parent references reach - // across @at-root rules, which comes unexpected - if (parents == NULL && head->has_real_parent_ref()) { - int i = pstack.size() - 1; - while (!parents && i > -1) { - parents = pstack.at(i--); - } - } - - if (parents && parents->length()) { - if (tails && tails->length() > 0) { - for (size_t n = 0, nL = tails->length(); n < nL; ++n) { - for (size_t i = 0, iL = parents->length(); i < iL; ++i) { - Complex_Selector_Obj t = (*tails)[n]; - Complex_Selector_Obj parent = (*parents)[i]; - Complex_Selector_Obj s = SASS_MEMORY_CLONE(parent); - Complex_Selector_Obj ss = SASS_MEMORY_CLONE(this); - ss->tail(t ? SASS_MEMORY_CLONE(t) : NULL); - Compound_Selector_Obj h = SASS_MEMORY_COPY(head_); - // remove parent selector from sequence - if (h->length()) { - h->erase(h->begin()); - ss->head(h); - } else { - ss->head(NULL); - } - // adjust for parent selector (1 char) - // if (h->length()) { - // ParserState state(h->at(0)->pstate()); - // state.offset.column += 1; - // state.column -= 1; - // (*h)[0]->pstate(state); - // } - // keep old parser state - s->pstate(pstate()); - // append new tail - s->append(ss, traces); - retval->append(s); - } - } - } - // have no tails but parents - // loop above is inside out - else { - for (size_t i = 0, iL = parents->length(); i < iL; ++i) { - Complex_Selector_Obj parent = (*parents)[i]; - Complex_Selector_Obj s = SASS_MEMORY_CLONE(parent); - Complex_Selector_Obj ss = SASS_MEMORY_CLONE(this); - // this is only if valid if the parent has no trailing op - // otherwise we cannot append more simple selectors to head - if (parent->last()->combinator() != ANCESTOR_OF) { - traces.push_back(Backtrace(pstate())); - throw Exception::InvalidParent(parent, traces, ss); - } - ss->tail(tail ? SASS_MEMORY_CLONE(tail) : NULL); - Compound_Selector_Obj h = SASS_MEMORY_COPY(head_); - // remove parent selector from sequence - if (h->length()) { - h->erase(h->begin()); - ss->head(h); - } else { - ss->head(NULL); - } - // \/ IMO ruby sass bug \/ - ss->has_line_feed(false); - // adjust for parent selector (1 char) - // if (h->length()) { - // ParserState state(h->at(0)->pstate()); - // state.offset.column += 1; - // state.column -= 1; - // (*h)[0]->pstate(state); - // } - // keep old parser state - s->pstate(pstate()); - // append new tail - s->append(ss, traces); - retval->append(s); - } - } - } - // have no parent but some tails - else { - if (tails && tails->length() > 0) { - for (size_t n = 0, nL = tails->length(); n < nL; ++n) { - Complex_Selector_Obj cpy = SASS_MEMORY_CLONE(this); - cpy->tail(SASS_MEMORY_CLONE(tails->at(n))); - cpy->head(SASS_MEMORY_NEW(Compound_Selector, head->pstate())); - for (size_t i = 1, L = this->head()->length(); i < L; ++i) - cpy->head()->append((*this->head())[i]); - if (!cpy->head()->length()) cpy->head(0); - retval->append(cpy->skip_empty_reference()); - } - } - // have no parent nor tails - else { - Complex_Selector_Obj cpy = SASS_MEMORY_CLONE(this); - cpy->head(SASS_MEMORY_NEW(Compound_Selector, head->pstate())); - for (size_t i = 1, L = this->head()->length(); i < L; ++i) - cpy->head()->append((*this->head())[i]); - if (!cpy->head()->length()) cpy->head(0); - retval->append(cpy->skip_empty_reference()); - } - } - } - // no parent selector in head - else { - retval = this->tails(tails); - } - - for (Simple_Selector_Obj ss : head->elements()) { - if (Wrapped_Selector_Ptr ws = Cast(ss)) { - if (Selector_List_Ptr sl = Cast(ws->selector())) { - if (parents) ws->selector(sl->resolve_parent_refs(pstack, traces, implicit_parent)); - } - } - } - - return retval.detach(); - - } - // has no head - return this->tails(tails); - } +#include "ast.hpp" - Selector_List_Ptr Complex_Selector::tails(Selector_List_Ptr tails) - { - Selector_List_Ptr rv = SASS_MEMORY_NEW(Selector_List, pstate_); - if (tails && tails->length()) { - for (size_t i = 0, iL = tails->length(); i < iL; ++i) { - Complex_Selector_Obj pr = SASS_MEMORY_CLONE(this); - pr->tail(tails->at(i)); - rv->append(pr); - } - } - else { - rv->append(this); - } - return rv; - } +namespace Sass { - // return the last tail that is defined - Complex_Selector_Obj Complex_Selector::first() - { - // declare variables used in loop - Complex_Selector_Obj cur = this; - Compound_Selector_Obj head; - // processing loop - while (cur) - { - // get the head - head = cur->head_; - // abort (and return) if it is not a parent selector - if (!head || head->length() != 1 || !Cast((*head)[0])) { - break; - } - // advance to next - cur = cur->tail_; - } - // result - return cur; - } + static Null sass_null(ParserState("null")); - // return the last tail that is defined - Complex_Selector_Obj Complex_Selector::last() - { - Complex_Selector_Ptr cur = this; - Complex_Selector_Ptr nxt = cur; - // loop until last - while (nxt) { - cur = nxt; - nxt = cur->tail(); - } - return cur; - } + 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"; + } + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// - Complex_Selector::Combinator Complex_Selector::clear_innermost() + void AST_Node::update_pstate(const ParserState& pstate) { - Combinator c; - if (!tail() || tail()->tail() == 0) - { c = combinator(); combinator(ANCESTOR_OF); tail(0); } - else - { c = tail()->clear_innermost(); } - return c; + pstate_.offset += pstate - pstate_ + pstate.offset; } - void Complex_Selector::set_innermost(Complex_Selector_Obj val, Combinator c) + std::string AST_Node::to_string(Sass_Inspect_Options opt) const { - if (!tail()) - { tail(val); combinator(c); } - else - { tail()->set_innermost(val, c); } + 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(); } - void Complex_Selector::cloneChildren() + std::string AST_Node::to_css(Sass_Inspect_Options opt) const { - if (head()) head(SASS_MEMORY_CLONE(head())); - if (tail()) tail(SASS_MEMORY_CLONE(tail())); + 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(); } - void Compound_Selector::cloneChildren() + std::string AST_Node::to_string() const { - for (size_t i = 0, l = length(); i < l; i++) { - at(i) = SASS_MEMORY_CLONE(at(i)); - } + return to_string({ NESTED, 5 }); } - void Selector_List::cloneChildren() - { - for (size_t i = 0, l = length(); i < l; i++) { - at(i) = SASS_MEMORY_CLONE(at(i)); - } - } + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// - void Wrapped_Selector::cloneChildren() - { - selector(SASS_MEMORY_CLONE(selector())); - } + Statement::Statement(ParserState 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_) + { } - // remove parent selector references - // basically unwraps parsed selectors - void Selector_List::remove_parent_selectors() + bool Statement::bubbles() { - // Check every rhs selector against left hand list - for(size_t i = 0, L = length(); i < L; ++i) { - if (!(*this)[i]->head()) continue; - if ((*this)[i]->head()->is_empty_reference()) { - // simply move to the next tail if we have "no" combinator - if ((*this)[i]->combinator() == Complex_Selector::ANCESTOR_OF) { - if ((*this)[i]->tail()) { - if ((*this)[i]->has_line_feed()) { - (*this)[i]->tail()->has_line_feed(true); - } - (*this)[i] = (*this)[i]->tail(); - } - } - // otherwise remove the first item from head - else { - (*this)[i]->head()->erase((*this)[i]->head()->begin()); - } - } - } + return false; } - size_t Wrapped_Selector::hash() - { - if (hash_ == 0) { - hash_combine(hash_, Simple_Selector::hash()); - if (selector_) hash_combine(hash_, selector_->hash()); - } - return hash_; - } - bool Wrapped_Selector::has_parent_ref() const { - // if (has_reference()) return true; - if (!selector()) return false; - return selector()->has_parent_ref(); - } - bool Wrapped_Selector::has_real_parent_ref() const { - // if (has_reference()) return true; - if (!selector()) return false; - return selector()->has_real_parent_ref(); - } - unsigned long Wrapped_Selector::specificity() const + bool Statement::has_content() { - return selector_ ? selector_->specificity() : 0; + return statement_type_ == CONTENT; } - - bool Selector_List::has_parent_ref() const + bool Statement::is_invisible() const { - for (Complex_Selector_Obj s : elements()) { - if (s && s->has_parent_ref()) return true; - } return false; } - bool Selector_List::has_real_parent_ref() const - { - for (Complex_Selector_Obj s : elements()) { - if (s && s->has_real_parent_ref()) return true; - } - return false; - } + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + Block::Block(ParserState 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 Selector_Schema::has_parent_ref() const + bool Block::isInvisible() const { - if (String_Schema_Obj schema = Cast(contents())) { - return schema->length() > 0 && Cast(schema->at(0)) != NULL; + for (auto& item : elements()) { + if (!item->is_invisible()) return false; } - return false; + return true; } - bool Selector_Schema::has_real_parent_ref() const + bool Block::has_content() { - if (String_Schema_Obj schema = Cast(contents())) { - Parent_Selector_Obj p = Cast(schema->at(0)); - return schema->length() > 0 && p && p->is_real_parent_ref(); + for (size_t i = 0, L = elements().size(); i < L; ++i) { + if (elements()[i]->has_content()) return true; } - return false; + return Statement::has_content(); } - void Selector_List::adjust_after_pushing(Complex_Selector_Obj c) + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + Has_Block::Has_Block(ParserState pstate, Block_Obj b) + : Statement(pstate), block_(b) + { } + Has_Block::Has_Block(const Has_Block* ptr) + : Statement(ptr), block_(ptr->block_) + { } + + bool Has_Block::has_content() { - // if (c->has_reference()) has_reference(true); + return (block_ && block_->has_content()) || Statement::has_content(); } - // it's a superselector if every selector of the right side - // list is a superselector of the given left side selector - bool Complex_Selector::is_superselector_of(Selector_List_Obj sub, std::string wrapping) - { - // Check every rhs selector against left hand list - for(size_t i = 0, L = sub->length(); i < L; ++i) { - if (!is_superselector_of((*sub)[i], wrapping)) return false; + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + Ruleset::Ruleset(ParserState pstate, SelectorListObj s, Block_Obj b) + : Has_Block(pstate, b), selector_(s), schema_(), is_root_(false) + { statement_type(RULESET); } + Ruleset::Ruleset(const Ruleset* ptr) + : Has_Block(ptr), + selector_(ptr->selector_), + schema_(ptr->schema_), + is_root_(ptr->is_root_) + { statement_type(RULESET); } + + bool Ruleset::is_invisible() const { + if (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; } - // it's a superselector if every selector of the right side - // list is a superselector of the given left side selector - bool Selector_List::is_superselector_of(Selector_List_Obj sub, std::string wrapping) + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + Bubble::Bubble(ParserState 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() { - // Check every rhs selector against left hand list - for(size_t i = 0, L = sub->length(); i < L; ++i) { - if (!is_superselector_of((*sub)[i], wrapping)) return false; - } return true; } - // it's a superselector if every selector on the right side - // is a superselector of any one of the left side selectors - bool Selector_List::is_superselector_of(Compound_Selector_Obj sub, std::string wrapping) + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + Trace::Trace(ParserState pstate, std::string n, Block_Obj b, char type) + : Has_Block(pstate, b), type_(type), name_(n) + { } + Trace::Trace(const Trace* ptr) + : Has_Block(ptr), + type_(ptr->type_), + name_(ptr->name_) + { } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + Directive::Directive(ParserState pstate, std::string kwd, SelectorListObj sel, Block_Obj b, Expression_Obj val) + : Has_Block(pstate, b), keyword_(kwd), selector_(sel), value_(val) // set value manually if needed + { statement_type(DIRECTIVE); } + Directive::Directive(const Directive* ptr) + : Has_Block(ptr), + keyword_(ptr->keyword_), + selector_(ptr->selector_), + value_(ptr->value_) // set value manually if needed + { statement_type(DIRECTIVE); } + + bool Directive::bubbles() { return is_keyframes() || is_media(); } + + bool Directive::is_media() { + return keyword_.compare("@-webkit-media") == 0 || + keyword_.compare("@-moz-media") == 0 || + keyword_.compare("@-o-media") == 0 || + keyword_.compare("@media") == 0; + } + bool Directive::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(ParserState pstate, Block_Obj b) + : Has_Block(pstate, b), name_() + { statement_type(KEYFRAMERULE); } + Keyframe_Rule::Keyframe_Rule(const Keyframe_Rule* ptr) + : Has_Block(ptr), name_(ptr->name_) + { statement_type(KEYFRAMERULE); } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + Declaration::Declaration(ParserState pstate, String_Obj prop, Expression_Obj val, bool i, bool c, Block_Obj b) + : Has_Block(pstate, b), property_(prop), value_(val), is_important_(i), is_custom_property_(c), is_indented_(false) + { statement_type(DECLARATION); } + Declaration::Declaration(const Declaration* ptr) + : Has_Block(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 { - // Check every lhs selector against right hand - for(size_t i = 0, L = length(); i < L; ++i) { - if ((*this)[i]->is_superselector_of(sub, wrapping)) return true; + if (is_custom_property()) return false; + return !(value_ && !Cast(value_)); + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + Assignment::Assignment(ParserState pstate, std::string var, Expression_Obj 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(ParserState pstate) + : Statement(pstate), + urls_(std::vector()), + incs_(std::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); } + + std::vector& Import::incs() { return incs_; } + std::vector& Import::urls() { return urls_; } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + Import_Stub::Import_Stub(ParserState 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_; }; + std::string Import_Stub::imp_path() { return resource_.imp_path; }; + std::string Import_Stub::abs_path() { return resource_.abs_path; }; + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + Warning::Warning(ParserState pstate, Expression_Obj msg) + : Statement(pstate), message_(msg) + { statement_type(WARNING); } + Warning::Warning(const Warning* ptr) + : Statement(ptr), message_(ptr->message_) + { statement_type(WARNING); } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + Error::Error(ParserState pstate, Expression_Obj msg) + : Statement(pstate), message_(msg) + { statement_type(ERROR); } + Error::Error(const Error* ptr) + : Statement(ptr), message_(ptr->message_) + { statement_type(ERROR); } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + Debug::Debug(ParserState pstate, Expression_Obj val) + : Statement(pstate), value_(val) + { statement_type(DEBUGSTMT); } + Debug::Debug(const Debug* ptr) + : Statement(ptr), value_(ptr->value_) + { statement_type(DEBUGSTMT); } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + Comment::Comment(ParserState 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; + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + If::If(ParserState pstate, Expression_Obj pred, Block_Obj con, Block_Obj alt) + : Has_Block(pstate, con), predicate_(pred), alternative_(alt) + { statement_type(IF); } + If::If(const If* ptr) + : Has_Block(ptr), + predicate_(ptr->predicate_), + alternative_(ptr->alternative_) + { statement_type(IF); } + + bool If::has_content() + { + return Has_Block::has_content() || (alternative_ && alternative_->has_content()); + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + For::For(ParserState pstate, + std::string var, Expression_Obj lo, Expression_Obj hi, Block_Obj b, bool inc) + : Has_Block(pstate, b), + variable_(var), lower_bound_(lo), upper_bound_(hi), is_inclusive_(inc) + { statement_type(FOR); } + For::For(const For* ptr) + : Has_Block(ptr), + variable_(ptr->variable_), + lower_bound_(ptr->lower_bound_), + upper_bound_(ptr->upper_bound_), + is_inclusive_(ptr->is_inclusive_) + { statement_type(FOR); } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + Each::Each(ParserState pstate, std::vector vars, Expression_Obj lst, Block_Obj b) + : Has_Block(pstate, b), variables_(vars), list_(lst) + { statement_type(EACH); } + Each::Each(const Each* ptr) + : Has_Block(ptr), variables_(ptr->variables_), list_(ptr->list_) + { statement_type(EACH); } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + While::While(ParserState pstate, Expression_Obj pred, Block_Obj b) + : Has_Block(pstate, b), predicate_(pred) + { statement_type(WHILE); } + While::While(const While* ptr) + : Has_Block(ptr), predicate_(ptr->predicate_) + { statement_type(WHILE); } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + Return::Return(ParserState pstate, Expression_Obj val) + : Statement(pstate), value_(val) + { statement_type(RETURN); } + Return::Return(const Return* ptr) + : Statement(ptr), value_(ptr->value_) + { statement_type(RETURN); } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + ExtendRule::ExtendRule(ParserState pstate, SelectorListObj s) + : Statement(pstate), isOptional_(false), selector_(s), schema_() + { statement_type(EXTEND); } + ExtendRule::ExtendRule(ParserState pstate, Selector_Schema_Obj s) + : Statement(pstate), isOptional_(false), selector_(), schema_(s) + { + statement_type(EXTEND); + } + ExtendRule::ExtendRule(const ExtendRule* ptr) + : Statement(ptr), + isOptional_(ptr->isOptional_), + selector_(ptr->selector_), + schema_(ptr->schema_) + { statement_type(EXTEND); } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + Definition::Definition(const Definition* ptr) + : Has_Block(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(ParserState pstate, + std::string n, + Parameters_Obj params, + Block_Obj b, + Type t) + : Has_Block(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(ParserState pstate, + Signature sig, + std::string n, + Parameters_Obj params, + Native_Function func_ptr, + bool overload_stub) + : Has_Block(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(ParserState pstate, + Signature sig, + std::string n, + Parameters_Obj params, + Sass_Function_Entry c_func) + : Has_Block(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(ParserState pstate, std::string n, Arguments_Obj args, Parameters_Obj b_params, Block_Obj b) + : Has_Block(pstate, b), name_(n), arguments_(args), block_parameters_(b_params) + { } + Mixin_Call::Mixin_Call(const Mixin_Call* ptr) + : Has_Block(ptr), + name_(ptr->name_), + arguments_(ptr->arguments_), + block_parameters_(ptr->block_parameters_) + { } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + Content::Content(ParserState 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(ParserState 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(ParserState pstate, Type t, Expression_Obj 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 std::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; } - return false; + catch (...) { throw; } } - - // it's a superselector if every selector on the right side - // is a superselector of any one of the left side selectors - bool Selector_List::is_superselector_of(Complex_Selector_Obj sub, std::string wrapping) + size_t Unary_Expression::hash() const { - // Check every lhs selector against right hand - for(size_t i = 0, L = length(); i < L; ++i) { - if ((*this)[i]->is_superselector_of(sub)) return true; - } - return false; + if (hash_ == 0) { + hash_ = std::hash()(optype_); + hash_combine(hash_, operand()->hash()); + }; + return hash_; } - Selector_List_Ptr Selector_List::unify_with(Selector_List_Ptr rhs) { - std::vector unified_complex_selectors; - // Unify all of children with RHS's children, storing the results in `unified_complex_selectors` - for (size_t lhs_i = 0, lhs_L = length(); lhs_i < lhs_L; ++lhs_i) { - Complex_Selector_Obj seq1 = (*this)[lhs_i]; - for(size_t rhs_i = 0, rhs_L = rhs->length(); rhs_i < rhs_L; ++rhs_i) { - Complex_Selector_Ptr seq2 = rhs->at(rhs_i); - - Selector_List_Obj result = seq1->unify_with(seq2); - if( result ) { - for(size_t i = 0, L = result->length(); i < L; ++i) { - unified_complex_selectors.push_back( (*result)[i] ); - } - } - } - } + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// - // Creates the final Selector_List by combining all the complex selectors - Selector_List_Ptr final_result = SASS_MEMORY_NEW(Selector_List, pstate()); - for (auto itr = unified_complex_selectors.begin(); itr != unified_complex_selectors.end(); ++itr) { - final_result->append(*itr); + Argument::Argument(ParserState pstate, Expression_Obj val, std::string n, bool rest, bool keyword) + : Expression(pstate), value_(val), name_(n), is_rest_argument_(rest), is_keyword_argument_(keyword), hash_(0) + { + if (!name_.empty() && is_rest_argument_) { + coreError("variable-length argument may not be passed by name", pstate_); } - return final_result; } - - void Selector_List::populate_extends(Selector_List_Obj extendee, Subset_Map& extends) + 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_) { - - Selector_List_Ptr extender = this; - for (auto complex_sel : extendee->elements()) { - Complex_Selector_Obj c = complex_sel; - - - // Ignore any parent selectors, until we find the first non Selectorerence head - Compound_Selector_Obj compound_sel = c->head(); - Complex_Selector_Obj pIter = complex_sel; - while (pIter) { - Compound_Selector_Obj pHead = pIter->head(); - if (pHead && Cast(pHead->elements()[0]) == NULL) { - compound_sel = pHead; - break; - } - - pIter = pIter->tail(); - } - - if (!pIter->head() || pIter->tail()) { - coreError("nested selectors may not be extended", c->pstate()); - } - - compound_sel->is_optional(extendee->is_optional()); - - for (size_t i = 0, L = extender->length(); i < L; ++i) { - extends.put(compound_sel, std::make_pair((*extender)[i], compound_sel)); - } + if (!name_.empty() && is_rest_argument_) { + coreError("variable-length argument may not be passed by name", pstate_); } - }; + } - void Compound_Selector::append(Simple_Selector_Ptr element) + void Argument::set_delayed(bool delayed) { - Vectorized::append(element); - pstate_.offset += element->pstate().offset; + if (value_) value_->set_delayed(delayed); + is_delayed(delayed); } - Compound_Selector_Ptr Compound_Selector::minus(Compound_Selector_Ptr rhs) + bool Argument::operator==(const Expression& rhs) const { - Compound_Selector_Ptr result = SASS_MEMORY_NEW(Compound_Selector, pstate()); - // result->has_parent_reference(has_parent_reference()); - - // not very efficient because it needs to preserve order - for (size_t i = 0, L = length(); i < L; ++i) + try { - bool found = false; - std::string thisSelector((*this)[i]->to_string()); - for (size_t j = 0, M = rhs->length(); j < M; ++j) - { - if (thisSelector == (*rhs)[j]->to_string()) - { - found = true; - break; - } - } - if (!found) result->append((*this)[i]); + const Argument* m = Cast(&rhs); + if (!(m && name() == m->name())) return false; + return *value() == *m->value(); + } + catch (std::bad_cast&) + { + return false; } + catch (...) { throw; } + } - return result; + size_t Argument::hash() const + { + if (hash_ == 0) { + hash_ = std::hash()(name()); + hash_combine(hash_, value()->hash()); + } + return hash_; } - void Compound_Selector::mergeSources(ComplexSelectorSet& sources) + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + Arguments::Arguments(ParserState 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 (ComplexSelectorSet::iterator iterator = sources.begin(), endIterator = sources.end(); iterator != endIterator; ++iterator) { - this->sources_.insert(SASS_MEMORY_CLONE(*iterator)); + for (Argument_Obj arg : elements()) { + if (arg) arg->set_delayed(delayed); } + is_delayed(delayed); } Argument_Obj Arguments::get_rest_argument() @@ -1749,7 +687,7 @@ namespace Sass { } } } - return NULL; + return {}; } Argument_Obj Arguments::get_keyword_argument() @@ -1761,7 +699,7 @@ namespace Sass { } } } - return NULL; + return {}; } void Arguments::adjust_after_pushing(Argument_Obj a) @@ -1797,403 +735,196 @@ namespace Sass { } } - bool Ruleset::is_invisible() const { - if (Selector_List_Ptr sl = Cast(selector())) { - for (size_t i = 0, L = sl->length(); i < L; ++i) - if (!(*sl)[i]->has_placeholder()) return false; - } - return true; - } - - bool Media_Block::is_invisible() const { - for (size_t i = 0, L = block()->length(); i < L; ++i) { - Statement_Obj stm = block()->at(i); - if (!stm->is_invisible()) return false; - } - return true; - } + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + Media_Query::Media_Query(ParserState 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(ParserState pstate, + Expression_Obj f, Expression_Obj 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(ParserState pstate, Expression_Obj f, Expression_Obj 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_) + { } - Number::Number(ParserState pstate, double val, std::string u, bool zero) - : Value(pstate), - Units(), - value_(val), - zero_(zero), - hash_(0) + bool At_Root_Query::exclude(std::string str) { - size_t l = 0; - size_t r; - if (!u.empty()) { - bool nominator = true; - while (true) { - r = u.find_first_of("*/", l); - std::string unit(u.substr(l, r == std::string::npos ? r : r - l)); - if (!unit.empty()) { - if (nominator) numerators.push_back(unit); - else denominators.push_back(unit); - } - if (r == std::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; + bool with = feature() && unquote(feature()->to_string()).compare("with") == 0; + List* l = static_cast(value().ptr()); + std::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; } - concrete_type(NUMBER); - } - - // cancel out unnecessary units - void Number::reduce() - { - // apply conversion factor - value_ *= this->Units::reduce(); } - void Number::normalize() - { - // apply conversion factor - value_ *= this->Units::normalize(); - } + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// - bool Custom_Warning::operator== (const Expression& rhs) const - { - if (Custom_Warning_Ptr_Const r = Cast(&rhs)) { - return message() == r->message(); - } - return false; - } + At_Root_Block::At_Root_Block(ParserState pstate, Block_Obj b, At_Root_Query_Obj e) + : Has_Block(pstate, b), expression_(e) + { statement_type(ATROOT); } + At_Root_Block::At_Root_Block(const At_Root_Block* ptr) + : Has_Block(ptr), expression_(ptr->expression_) + { statement_type(ATROOT); } - bool Custom_Error::operator== (const Expression& rhs) const - { - if (Custom_Error_Ptr_Const r = Cast(&rhs)) { - return message() == r->message(); - } - return false; + bool At_Root_Block::bubbles() { + return true; } - bool Number::operator== (const Expression& rhs) const - { - if (auto rhsnr = Cast(&rhs)) { - return *this == *rhsnr; + bool At_Root_Block::exclude_node(Statement_Obj s) { + if (expression() == nullptr) + { + return s->statement_type() == Statement::RULESET; } - return false; - } - bool Number::operator== (const Number& rhs) const - { - 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(); - // unitless and only having one unit seems equivalent (will change in future) - if (!lhs_units || !rhs_units) { - return NEAR_EQUAL(l.value(), r.value()); + if (s->statement_type() == Statement::DIRECTIVE) + { + if (Directive_Obj dir = Cast(s)) + { + std::string keyword(dir->keyword()); + if (keyword.length() > 0) keyword.erase(0, 1); + return expression()->exclude(keyword); + } } - l.normalize(); r.normalize(); - Units &lhs_unit = l, &rhs_unit = r; - return lhs_unit == rhs_unit && - NEAR_EQUAL(l.value(), r.value()); - } - - bool Number::operator< (const Number& rhs) const - { - 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(); - // unitless and only having one unit seems equivalent (will change in future) - if (!lhs_units || !rhs_units) { - return l.value() < r.value(); + if (s->statement_type() == Statement::MEDIA) + { + return expression()->exclude("media"); } - l.normalize(); r.normalize(); - Units &lhs_unit = l, &rhs_unit = r; - if (!(lhs_unit == rhs_unit)) { - /* ToDo: do we always get usefull backtraces? */ - throw Exception::IncompatibleUnits(rhs, *this); + if (s->statement_type() == Statement::RULESET) + { + return expression()->exclude("rule"); } - return lhs_unit < rhs_unit || - l.value() < r.value(); - } - - bool String_Quoted::operator== (const Expression& rhs) const - { - if (String_Quoted_Ptr_Const qstr = Cast(&rhs)) { - return (value() == qstr->value()); - } else if (String_Constant_Ptr_Const cstr = Cast(&rhs)) { - return (value() == cstr->value()); + if (s->statement_type() == Statement::SUPPORTS) + { + return expression()->exclude("supports"); } - return false; - } - - bool String_Constant::is_invisible() const { - return value_.empty() && quote_mark_ == 0; - } - - bool String_Constant::operator== (const Expression& rhs) const - { - if (String_Quoted_Ptr_Const qstr = Cast(&rhs)) { - return (value() == qstr->value()); - } else if (String_Constant_Ptr_Const cstr = Cast(&rhs)) { - return (value() == cstr->value()); + if (Directive_Obj dir = Cast(s)) + { + if (dir->is_keyframes()) return expression()->exclude("keyframes"); } return false; } - bool String_Schema::is_left_interpolant(void) 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 (String_Schema_Ptr_Const r = Cast(&rhs)) { - if (length() != r->length()) return false; - for (size_t i = 0, L = length(); i < L; ++i) { - Expression_Obj rv = (*r)[i]; - Expression_Obj lv = (*this)[i]; - if (!lv || !rv) return false; - if (!(*lv == *rv)) return false; - } - return true; - } - return false; - } + Parameter::Parameter(ParserState pstate, std::string n, Expression_Obj 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_) + { } - bool Boolean::operator== (const Expression& rhs) const - { - if (Boolean_Ptr_Const r = Cast(&rhs)) { - return (value() == r->value()); - } - return false; - } + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// - bool Color::operator== (const Expression& rhs) const - { - if (Color_Ptr_Const r = Cast(&rhs)) { - return r_ == r->r() && - g_ == r->g() && - b_ == r->b() && - a_ == r->a(); - } - return false; - } + Parameters::Parameters(ParserState 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_) + { } - bool List::operator== (const Expression& rhs) const + void Parameters::adjust_after_pushing(Parameter_Obj p) { - if (List_Ptr_Const 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) { - Expression_Obj rv = r->at(i); - Expression_Obj lv = this->at(i); - if (!lv || !rv) return false; - if (!(*lv == *rv)) return false; + if (p->default_value()) { + if (has_rest_parameter()) { + coreError("optional parameters may not be combined with variable-length parameters", p->pstate()); } - return true; + has_optional_parameters(true); } - return false; - } - - bool Map::operator== (const Expression& rhs) const - { - if (Map_Ptr_Const r = Cast(&rhs)) { - if (length() != r->length()) return false; - for (auto key : keys()) { - Expression_Obj lv = at(key); - Expression_Obj rv = r->at(key); - if (!rv || !lv) return false; - if (!(*lv == *rv)) return false; + else if (p->is_rest_parameter()) { + if (has_rest_parameter()) { + coreError("functions and mixins cannot have more than one variable-length parameter", p->pstate()); } - return true; - } - return false; - } - - bool Null::operator== (const Expression& rhs) const - { - return rhs.concrete_type() == NULL_VAL; - } - - bool Function::operator== (const Expression& rhs) const - { - if (Function_Ptr_Const r = Cast(&rhs)) { - Definition_Ptr_Const d1 = Cast(definition()); - Definition_Ptr_Const d2 = Cast(r->definition()); - return d1 && d2 && d1 == d2 && is_css() == r->is_css(); + has_rest_parameter(true); } - 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) { - Expression_Obj obj = this->at(i); - if (Argument_Ptr arg = Cast(obj)) { - if (!arg->name().empty()) return i; + else { + if (has_rest_parameter()) { + coreError("required parameters must precede variable-length parameters", p->pstate()); } - } - return length(); - } - - Expression_Obj Hashed::at(Expression_Obj k) const - { - if (elements_.count(k)) - { return elements_.at(k); } - else { return NULL; } - } - - bool Binary_Expression::is_left_interpolant(void) const - { - return is_interpolant() || (left() && left()->is_left_interpolant()); - } - bool Binary_Expression::is_right_interpolant(void) const - { - return is_interpolant() || (right() && right()->is_right_interpolant()); - } - - const std::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(); - } - - const std::string AST_Node::to_string() const - { - return to_string({ NESTED, 5 }); - } - - std::string String_Quoted::inspect() const - { - return quote(value_, '*'); - } - - std::string String_Constant::inspect() const - { - return quote(value_, '*'); - } - - bool Declaration::is_invisible() const - { - if (is_custom_property()) return false; - - return !(value_ && value_->concrete_type() != Expression::NULL_VAL); - } - - ////////////////////////////////////////////////////////////////////////////////////////// - // Additional method on Lists to retrieve values directly or from an encompassed Argument. - ////////////////////////////////////////////////////////////////////////////////////////// - Expression_Obj List::value_at_index(size_t i) { - Expression_Obj obj = this->at(i); - if (is_arglist_) { - if (Argument_Ptr arg = Cast(obj)) { - return arg->value(); - } else { - return obj; + if (has_optional_parameters()) { + coreError("required parameters must precede optional parameters", p->pstate()); } - } else { - return obj; } } - ////////////////////////////////////////////////////////////////////////////////////////// - // Convert map to (key, value) list. - ////////////////////////////////////////////////////////////////////////////////////////// - List_Obj Map::to_list(ParserState& pstate) { - List_Obj ret = SASS_MEMORY_NEW(List, pstate, length(), SASS_COMMA); - - for (auto key : keys()) { - List_Obj l = SASS_MEMORY_NEW(List, pstate, 2); - l->append(key); - l->append(at(key)); - ret->append(l); - } + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// - return ret; - } + // If you forget to add a class here you will get + // undefined reference to `vtable for Sass::Class' - ////////////////////////////////////////////////////////////////////////////////////////// - // Copy implementations - ////////////////////////////////////////////////////////////////////////////////////////// - - #ifdef DEBUG_SHARED_PTR - - #define IMPLEMENT_AST_OPERATORS(klass) \ - klass##_Ptr klass::copy(std::string file, size_t line) const { \ - klass##_Ptr cpy = new klass(this); \ - cpy->trace(file, line); \ - return cpy; \ - } \ - klass##_Ptr klass::clone(std::string file, size_t line) const { \ - klass##_Ptr cpy = copy(file, line); \ - cpy->cloneChildren(); \ - return cpy; \ - } \ - - #else - - #define IMPLEMENT_AST_OPERATORS(klass) \ - klass##_Ptr klass::copy() const { \ - return new klass(this); \ - } \ - klass##_Ptr klass::clone() const { \ - klass##_Ptr cpy = copy(); \ - cpy->cloneChildren(); \ - return cpy; \ - } \ - - #endif - - IMPLEMENT_AST_OPERATORS(Supports_Operator); - IMPLEMENT_AST_OPERATORS(Supports_Negation); - IMPLEMENT_AST_OPERATORS(Compound_Selector); - IMPLEMENT_AST_OPERATORS(Complex_Selector); - IMPLEMENT_AST_OPERATORS(Element_Selector); - IMPLEMENT_AST_OPERATORS(Class_Selector); - IMPLEMENT_AST_OPERATORS(Id_Selector); - IMPLEMENT_AST_OPERATORS(Pseudo_Selector); - IMPLEMENT_AST_OPERATORS(Wrapped_Selector); - IMPLEMENT_AST_OPERATORS(Selector_List); IMPLEMENT_AST_OPERATORS(Ruleset); - IMPLEMENT_AST_OPERATORS(Media_Block); - IMPLEMENT_AST_OPERATORS(Custom_Warning); - IMPLEMENT_AST_OPERATORS(Custom_Error); - IMPLEMENT_AST_OPERATORS(List); - IMPLEMENT_AST_OPERATORS(Map); - IMPLEMENT_AST_OPERATORS(Function); - IMPLEMENT_AST_OPERATORS(Number); - IMPLEMENT_AST_OPERATORS(Binary_Expression); - IMPLEMENT_AST_OPERATORS(String_Schema); - IMPLEMENT_AST_OPERATORS(String_Constant); - IMPLEMENT_AST_OPERATORS(String_Quoted); - IMPLEMENT_AST_OPERATORS(Boolean); - IMPLEMENT_AST_OPERATORS(Color); - IMPLEMENT_AST_OPERATORS(Null); - IMPLEMENT_AST_OPERATORS(Parent_Selector); + IMPLEMENT_AST_OPERATORS(MediaRule); + IMPLEMENT_AST_OPERATORS(CssMediaRule); + IMPLEMENT_AST_OPERATORS(CssMediaQuery); IMPLEMENT_AST_OPERATORS(Import); IMPLEMENT_AST_OPERATORS(Import_Stub); - IMPLEMENT_AST_OPERATORS(Function_Call); IMPLEMENT_AST_OPERATORS(Directive); IMPLEMENT_AST_OPERATORS(At_Root_Block); - IMPLEMENT_AST_OPERATORS(Supports_Block); IMPLEMENT_AST_OPERATORS(While); IMPLEMENT_AST_OPERATORS(Each); IMPLEMENT_AST_OPERATORS(For); IMPLEMENT_AST_OPERATORS(If); IMPLEMENT_AST_OPERATORS(Mixin_Call); - IMPLEMENT_AST_OPERATORS(Extension); + IMPLEMENT_AST_OPERATORS(ExtendRule); IMPLEMENT_AST_OPERATORS(Media_Query); IMPLEMENT_AST_OPERATORS(Media_Query_Expression); IMPLEMENT_AST_OPERATORS(Debug); @@ -2202,25 +933,21 @@ namespace Sass { IMPLEMENT_AST_OPERATORS(Assignment); IMPLEMENT_AST_OPERATORS(Return); IMPLEMENT_AST_OPERATORS(At_Root_Query); - IMPLEMENT_AST_OPERATORS(Variable); IMPLEMENT_AST_OPERATORS(Comment); - IMPLEMENT_AST_OPERATORS(Attribute_Selector); - IMPLEMENT_AST_OPERATORS(Supports_Interpolation); - IMPLEMENT_AST_OPERATORS(Supports_Declaration); - IMPLEMENT_AST_OPERATORS(Supports_Condition); IMPLEMENT_AST_OPERATORS(Parameters); IMPLEMENT_AST_OPERATORS(Parameter); IMPLEMENT_AST_OPERATORS(Arguments); IMPLEMENT_AST_OPERATORS(Argument); IMPLEMENT_AST_OPERATORS(Unary_Expression); - IMPLEMENT_AST_OPERATORS(Function_Call_Schema); IMPLEMENT_AST_OPERATORS(Block); IMPLEMENT_AST_OPERATORS(Content); IMPLEMENT_AST_OPERATORS(Trace); IMPLEMENT_AST_OPERATORS(Keyframe_Rule); IMPLEMENT_AST_OPERATORS(Bubble); - IMPLEMENT_AST_OPERATORS(Selector_Schema); - IMPLEMENT_AST_OPERATORS(Placeholder_Selector); IMPLEMENT_AST_OPERATORS(Definition); IMPLEMENT_AST_OPERATORS(Declaration); + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + } diff --git a/src/libsass/src/ast.hpp b/src/libsass/src/ast.hpp index a2be8685c..9ae2d27dd 100644 --- a/src/libsass/src/ast.hpp +++ b/src/libsass/src/ast.hpp @@ -1,83 +1,26 @@ #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 -#include -#include + #include -#include +#include + #include "sass/base.h" +#include "ast_helpers.hpp" #include "ast_fwd_decl.hpp" +#include "ast_def_macros.hpp" -#ifdef DEBUG_SHARED_PTR - -#define ATTACH_VIRTUAL_AST_OPERATIONS(klass) \ - virtual klass##_Ptr copy(std::string, size_t) const = 0; \ - virtual klass##_Ptr clone(std::string, size_t) const = 0; \ - -#define ATTACH_AST_OPERATIONS(klass) \ - virtual klass##_Ptr copy(std::string, size_t) const; \ - virtual klass##_Ptr clone(std::string, size_t) const; \ - -#else - -#define ATTACH_VIRTUAL_AST_OPERATIONS(klass) \ - virtual klass##_Ptr copy() const = 0; \ - virtual klass##_Ptr clone() const = 0; \ - -#define ATTACH_AST_OPERATIONS(klass) \ - virtual klass##_Ptr copy() const; \ - virtual klass##_Ptr clone() const; \ - -#endif - -#ifdef __clang__ - -/* - * There are some overloads used here that trigger the clang overload - * hiding warning. Specifically: - * - * Type type() which hides string type() from Expression - * - */ -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Woverloaded-virtual" - -#endif - -#include "util.hpp" -#include "units.hpp" -#include "context.hpp" +#include "file.hpp" #include "position.hpp" -#include "constants.hpp" #include "operation.hpp" -#include "position.hpp" -#include "inspect.hpp" -#include "source_map.hpp" #include "environment.hpp" -#include "error_handling.hpp" -#include "ast_def_macros.hpp" -#include "ast_fwd_decl.hpp" -#include "source_map.hpp" - -#include "sass.h" +#include "fn_utils.hpp" namespace Sass { - // easier to search with name - const bool DELAYED = true; - - // ToDo: should this really be hardcoded - // Note: most methods follow precision option - const double NUMBER_EPSILON = 0.00000000000001; - - // macro to test if numbers are equal within a small error margin - #define NEAR_EQUAL(lhs, rhs) std::fabs(lhs - rhs) < NUMBER_EPSILON - // ToDo: where does this fit best? // We don't share this with C-API? class Operand { @@ -104,6 +47,10 @@ namespace Sass { } ////////////////////////////////////////////////////////// + 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. ////////////////////////////////////////////////////////// @@ -117,25 +64,44 @@ namespace Sass { : pstate_(ptr->pstate_) { } + // allow implicit conversion to string + // needed for by SharedPtr implementation + operator std::string() { + return to_string(); + } + // AST_Node(AST_Node& ptr) = delete; virtual ~AST_Node() = 0; - virtual size_t hash() { return 0; } - ATTACH_VIRTUAL_AST_OPERATIONS(AST_Node); + virtual size_t hash() const { return 0; } virtual std::string inspect() const { return to_string({ INSPECT, 5 }); } virtual std::string to_sass() const { return to_string({ TO_SASS, 5 }); } - virtual const std::string to_string(Sass_Inspect_Options opt) const; - virtual const std::string to_string() const; + virtual std::string to_string(Sass_Inspect_Options opt) const; + virtual std::string to_css(Sass_Inspect_Options opt) const; + virtual std::string to_string() const; virtual void cloneChildren() {}; // generic find function (not fully implemented yet) // ToDo: add specific implementions to all children virtual bool find ( bool (*f)(AST_Node_Obj) ) { return f(this); }; - public: void update_pstate(const ParserState& pstate); - public: Offset off() { return pstate(); } Position pos() { return pstate(); } - ATTACH_OPERATIONS() + + // Some obects are not meant to be compared + // ToDo: maybe fallback to pointer comparison? + virtual bool operator== (const AST_Node& rhs) const { + throw std::runtime_error("operator== not implemented"); + } + + // We can give some reasonable implementations by using + // inverst operators on the specialized implementations + virtual bool operator!= (const AST_Node& rhs) const { + // Unequal if not equal + return !(*this == rhs); + } + + ATTACH_ABSTRACT_AST_OPERATIONS(AST_Node); + ATTACH_ABSTRACT_CRTP_PERFORM_METHODS() }; inline AST_Node::~AST_Node() { } @@ -162,7 +128,7 @@ namespace Sass { ////////////////////////////////////////////////////////////////////// class Expression : public AST_Node { public: - enum Concrete_Type { + enum Type { NONE, BOOLEAN, NUMBER, @@ -177,86 +143,39 @@ namespace Sass { C_ERROR, FUNCTION, VARIABLE, + PARENT, NUM_TYPES }; - enum Simple_Type { - SIMPLE, - ATTR_SEL, - PSEUDO_SEL, - WRAPPED_SEL, - }; 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(Concrete_Type, concrete_type) + ADD_PROPERTY(Type, concrete_type) public: - Expression(ParserState pstate, - bool d = false, bool e = false, bool i = false, Concrete_Type ct = NONE) - : AST_Node(pstate), - is_delayed_(d), - is_expanded_(e), - is_interpolant_(i), - concrete_type_(ct) - { } - 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_) - { } + Expression(ParserState pstate, bool d = false, bool e = false, bool i = false, Type ct = NONE); virtual operator bool() { return true; } virtual ~Expression() { } - virtual std::string type() const { return ""; /* TODO: raise an error? */ } virtual bool is_invisible() const { return false; } + + virtual std::string type() const { return ""; } static std::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(); } - virtual std::string inspect() const { return to_string({ INSPECT, 5 }); } - virtual std::string to_sass() const { return to_string({ TO_SASS, 5 }); } ATTACH_VIRTUAL_AST_OPERATIONS(Expression); - virtual size_t hash() { return 0; } - }; - - ////////////////////////////////////////////////////////////////////// - // Still just an expression, but with a to_string method - ////////////////////////////////////////////////////////////////////// - class PreValue : public Expression { - public: - PreValue(ParserState pstate, - bool d = false, bool e = false, bool i = false, Concrete_Type ct = NONE) - : Expression(pstate, d, e, i, ct) - { } - PreValue(const PreValue* ptr) - : Expression(ptr) - { } - ATTACH_VIRTUAL_AST_OPERATIONS(PreValue); - virtual ~PreValue() { } + size_t hash() const override { return 0; } }; - ////////////////////////////////////////////////////////////////////// - // base class for values that support operations - ////////////////////////////////////////////////////////////////////// - class Value : public Expression { - public: - Value(ParserState pstate, - bool d = false, bool e = false, bool i = false, Concrete_Type ct = NONE) - : Expression(pstate, d, e, i, ct) - { } - Value(const Value* ptr) - : Expression(ptr) - { } - ATTACH_VIRTUAL_AST_OPERATIONS(Value); - virtual bool operator== (const Expression& rhs) const = 0; - }; } ///////////////////////////////////////////////////////////////////////////////////// @@ -293,59 +212,141 @@ namespace Sass { class Vectorized { std::vector elements_; protected: - size_t hash_; + mutable size_t hash_; void reset_hash() { hash_ = 0; } virtual void adjust_after_pushing(T element) { } public: - Vectorized(size_t s = 0) : elements_(std::vector()), hash_(0) + Vectorized(size_t s = 0) : hash_(0) { elements_.reserve(s); } + Vectorized(std::vector vec) : + elements_(std::move(vec)), + hash_(0) + {} virtual ~Vectorized() = 0; size_t length() const { return elements_.size(); } bool empty() const { return elements_.empty(); } void clear() { return elements_.clear(); } - T last() const { return elements_.back(); } - T first() const { return elements_.front(); } + T& last() { return elements_.back(); } + T& first() { return elements_.front(); } + const T& last() const { return elements_.back(); } + const T& first() const { return elements_.front(); } + + bool operator== (const Vectorized& rhs) const { + // Abort early if sizes do not match + if (length() != rhs.length()) return false; + // Otherwise test each node for object equalicy in order + return std::equal(begin(), end(), rhs.begin(), ObjEqualityFn); + } + + bool operator!= (const Vectorized& rhs) const { + return !(*this == rhs); + } + T& operator[](size_t i) { return elements_[i]; } virtual const T& at(size_t i) const { return elements_.at(i); } virtual T& at(size_t i) { return elements_.at(i); } + const T& get(size_t i) const { return elements_[i]; } const T& operator[](size_t i) const { return elements_[i]; } - virtual void append(T element) + + // Implicitly get the std::vector from our object + // Makes the Vector directly assignable to std::vector + // You are responsible to make a copy if needed + // Note: since this returns the real object, we can't + // Note: guarantee that the hash will not get out of sync + operator std::vector&() { return elements_; } + operator const std::vector&() const { return elements_; } + + // Explicitly request all elements as a real std::vector + // You are responsible to make a copy if needed + // Note: since this returns the real object, we can't + // Note: guarantee that the hash will not get out of sync + std::vector& elements() { return elements_; } + const std::vector& elements() const { return elements_; } + + // Insert all items from compatible vector + void concat(const std::vector& v) + { + if (!v.empty()) reset_hash(); + elements().insert(end(), v.begin(), v.end()); + } + + // Syntatic sugar for pointers + void concat(const Vectorized* v) { - if (element) { - reset_hash(); - elements_.push_back(element); - adjust_after_pushing(element); + if (v != nullptr) { + return concat(*v); } } - virtual void concat(Vectorized* v) + + // Insert one item on the front + void unshift(T element) { - for (size_t i = 0, L = v->length(); i < L; ++i) this->append((*v)[i]); + reset_hash(); + elements_.insert(begin(), element); + } + + // Remove and return item on the front + // ToDo: handle empty vectors + T shift() { + reset_hash(); + T first = get(0); + elements_.erase(begin()); + return first; } - Vectorized& unshift(T element) + + // Insert one item on the back + // ToDo: rename this to push + void append(T element) { - elements_.insert(elements_.begin(), element); - return *this; + reset_hash(); + elements_.insert(end(), element); + // ToDo: Mostly used by parameters and arguments + // ToDo: Find a more elegant way to support this + adjust_after_pushing(element); + } + + // Check if an item already exists + // Uses underlying object `operator==` + // E.g. compares the actual objects + bool contains(const T& el) const { + for (const T& rhs : elements_) { + // Test the underlying objects for equality + // A std::find checks for pointer equality + if (ObjEqualityFn(el, rhs)) { + return true; + } + } + return false; + } + + // This might be better implemented as `operator=`? + void elements(std::vector e) { + reset_hash(); + elements_ = std::move(e); } - std::vector& elements() { return elements_; } - const std::vector& elements() const { return elements_; } - std::vector& elements(std::vector& e) { elements_ = e; return elements_; } - virtual size_t hash() + virtual size_t hash() const { if (hash_ == 0) { - for (T& el : elements_) { + for (const T& el : elements_) { hash_combine(hash_, el->hash()); } } return hash_; } + template + typename std::vector::iterator insert(P position, const V& val) { + reset_hash(); + return elements_.insert(position, val); + } + typename std::vector::iterator end() { return elements_.end(); } typename std::vector::iterator begin() { return elements_.begin(); } typename std::vector::const_iterator end() const { return elements_.end(); } typename std::vector::const_iterator begin() const { return elements_.begin(); } - typename std::vector::iterator erase(typename std::vector::iterator el) { return elements_.erase(el); } - typename std::vector::const_iterator erase(typename std::vector::const_iterator el) { return elements_.erase(el); } + typename std::vector::iterator erase(typename std::vector::iterator el) { reset_hash(); return elements_.erase(el); } + typename std::vector::const_iterator erase(typename std::vector::const_iterator el) { reset_hash(); return elements_.erase(el); } }; template @@ -355,36 +356,61 @@ namespace Sass { // Mixin class for AST nodes that should behave like a hash table. Uses an // extra internally to maintain insertion order for interation. ///////////////////////////////////////////////////////////////////////////// + template class Hashed { private: - ExpressionMap elements_; - std::vector list_; + std::unordered_map< + K, T, ObjHash, ObjEquality + > elements_; + + std::vector _keys; + std::vector _values; protected: - size_t hash_; - Expression_Obj duplicate_key_; + mutable size_t hash_; + K duplicate_key_; void reset_hash() { hash_ = 0; } - void reset_duplicate_key() { duplicate_key_ = 0; } - virtual void adjust_after_pushing(std::pair p) { } + void reset_duplicate_key() { duplicate_key_ = {}; } + virtual void adjust_after_pushing(std::pair p) { } public: Hashed(size_t s = 0) - : elements_(ExpressionMap(s)), - list_(std::vector()), - hash_(0), duplicate_key_(NULL) - { elements_.reserve(s); list_.reserve(s); } + : elements_(), + _keys(), + _values(), + hash_(0), duplicate_key_({}) + { + _keys.reserve(s); + _values.reserve(s); + elements_.reserve(s); + } virtual ~Hashed(); - size_t length() const { return list_.size(); } - bool empty() const { return list_.empty(); } - bool has(Expression_Obj k) const { return elements_.count(k) == 1; } - Expression_Obj at(Expression_Obj k) const; - bool has_duplicate_key() const { return duplicate_key_ != 0; } - Expression_Obj get_duplicate_key() const { return duplicate_key_; } - const ExpressionMap elements() { return elements_; } - Hashed& operator<<(std::pair p) + 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, ObjEquality + >& elements() { return elements_; } + Hashed& operator<<(std::pair p) { reset_hash(); - if (!has(p.first)) list_.push_back(p.first); - else if (!duplicate_key_) duplicate_key_ = p.first; + if (!has(p.first)) { + _keys.push_back(p.first); + _values.push_back(p.second); + } + else if (!duplicate_key_) { + duplicate_key_ = p.first; + } elements_[p.first] = p.second; @@ -395,7 +421,8 @@ namespace Sass { { if (length() == 0) { this->elements_ = h->elements_; - this->list_ = h->list_; + this->_values = h->_values; + this->_keys = h->_keys; return *this; } @@ -406,8 +433,12 @@ namespace Sass { reset_duplicate_key(); return *this; } - const ExpressionMap& pairs() const { return elements_; } - const std::vector& keys() const { return list_; } + const std::unordered_map< + K, T, ObjHash, ObjEquality + >& pairs() const { return elements_; } + + const std::vector& keys() const { return _keys; } + const std::vector& values() const { return _values; } // std::unordered_map::iterator end() { return elements_.end(); } // std::unordered_map::iterator begin() { return elements_.begin(); } @@ -415,8 +446,8 @@ namespace Sass { // std::unordered_map::const_iterator begin() const { return elements_.begin(); } }; - inline Hashed::~Hashed() { } - + template + inline Hashed::~Hashed() { } ///////////////////////////////////////////////////////////////////////// // Abstract base class for statements. This side of the AST hierarchy @@ -425,7 +456,7 @@ namespace Sass { ///////////////////////////////////////////////////////////////////////// class Statement : public AST_Node { public: - enum Statement_Type { + enum Type { NONE, RULESET, MEDIA, @@ -451,60 +482,34 @@ namespace Sass { IF }; private: - ADD_PROPERTY(Statement_Type, statement_type) + ADD_PROPERTY(Type, statement_type) ADD_PROPERTY(size_t, tabs) ADD_PROPERTY(bool, group_end) public: - Statement(ParserState pstate, Statement_Type st = NONE, size_t t = 0) - : AST_Node(pstate), statement_type_(st), tabs_(t), group_end_(false) - { } - Statement(const Statement* ptr) - : AST_Node(ptr), - statement_type_(ptr->statement_type_), - tabs_(ptr->tabs_), - group_end_(ptr->group_end_) - { } - virtual ~Statement() = 0; + Statement(ParserState pstate, Type st = NONE, size_t t = 0); + virtual ~Statement() = 0; // virtual destructor // needed for rearranging nested rulesets during CSS emission - virtual bool is_invisible() const { return false; } - virtual bool bubbles() { return false; } - virtual bool has_content() - { - return statement_type_ == CONTENT; - } + 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 : public Statement, public Vectorized { + 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) - { - } + void adjust_after_pushing(Statement_Obj s) override {} public: - Block(ParserState pstate, size_t s = 0, bool r = false) - : Statement(pstate), - Vectorized(s), - is_root_(r) - { } - Block(const Block* ptr) - : Statement(ptr), - Vectorized(*ptr), - is_root_(ptr->is_root_) - { } - virtual bool has_content() - { - for (size_t i = 0, L = elements().size(); i < L; ++i) { - if (elements()[i]->has_content()) return true; - } - return Statement::has_content(); - } + Block(ParserState pstate, size_t s = 0, bool r = false); + bool isInvisible() const; + bool has_content() override; ATTACH_AST_OPERATIONS(Block) - ATTACH_OPERATIONS() + ATTACH_CRTP_PERFORM_METHODS() }; //////////////////////////////////////////////////////////////////////// @@ -513,17 +518,10 @@ namespace Sass { class Has_Block : public Statement { ADD_PROPERTY(Block_Obj, block) public: - Has_Block(ParserState pstate, Block_Obj b) - : Statement(pstate), block_(b) - { } - Has_Block(const Has_Block* ptr) - : Statement(ptr), block_(ptr->block_) - { } - virtual bool has_content() - { - return (block_ && block_->has_content()) || Statement::has_content(); - } - virtual ~Has_Block() = 0; + Has_Block(ParserState pstate, Block_Obj b); + Has_Block(const Has_Block* ptr); // copy constructor + virtual ~Has_Block() = 0; // virtual destructor + virtual bool has_content() override; }; inline Has_Block::~Has_Block() { } @@ -531,422 +529,243 @@ namespace Sass { // Rulesets (i.e., sets of styles headed by a selector and containing a block // of style declarations. ///////////////////////////////////////////////////////////////////////////// - class Ruleset : public Has_Block { - ADD_PROPERTY(Selector_List_Obj, selector) + class Ruleset final : public Has_Block { + ADD_PROPERTY(SelectorListObj, selector) + ADD_PROPERTY(Selector_Schema_Obj, schema) ADD_PROPERTY(bool, is_root); public: - Ruleset(ParserState pstate, Selector_List_Obj s = 0, Block_Obj b = 0) - : Has_Block(pstate, b), selector_(s), is_root_(false) - { statement_type(RULESET); } - Ruleset(const Ruleset* ptr) - : Has_Block(ptr), - selector_(ptr->selector_), - is_root_(ptr->is_root_) - { statement_type(RULESET); } - bool is_invisible() const; + Ruleset(ParserState pstate, SelectorListObj s = {}, Block_Obj b = {}); + bool is_invisible() const override; ATTACH_AST_OPERATIONS(Ruleset) - ATTACH_OPERATIONS() + ATTACH_CRTP_PERFORM_METHODS() }; ///////////////// // Bubble. ///////////////// - class Bubble : public Statement { + class Bubble final : public Statement { ADD_PROPERTY(Statement_Obj, node) ADD_PROPERTY(bool, group_end) public: - Bubble(ParserState pstate, Statement_Obj n, Statement_Obj g = 0, size_t t = 0) - : Statement(pstate, Statement::BUBBLE, t), node_(n), group_end_(g == 0) - { } - Bubble(const Bubble* ptr) - : Statement(ptr), - node_(ptr->node_), - group_end_(ptr->group_end_) - { } - bool bubbles() { return true; } + Bubble(ParserState pstate, Statement_Obj n, Statement_Obj g = {}, size_t t = 0); + bool bubbles() override; ATTACH_AST_OPERATIONS(Bubble) - ATTACH_OPERATIONS() + ATTACH_CRTP_PERFORM_METHODS() }; ///////////////// // Trace. ///////////////// - class Trace : public Has_Block { + class Trace final : public Has_Block { ADD_CONSTREF(char, type) ADD_CONSTREF(std::string, name) public: - Trace(ParserState pstate, std::string n, Block_Obj b = 0, char type = 'm') - : Has_Block(pstate, b), type_(type), name_(n) - { } - Trace(const Trace* ptr) - : Has_Block(ptr), - type_(ptr->type_), - name_(ptr->name_) - { } + Trace(ParserState pstate, std::string n, Block_Obj b = {}, char type = 'm'); ATTACH_AST_OPERATIONS(Trace) - ATTACH_OPERATIONS() - }; - - ///////////////// - // Media queries. - ///////////////// - class Media_Block : public Has_Block { - ADD_PROPERTY(List_Obj, media_queries) - public: - Media_Block(ParserState pstate, List_Obj mqs, Block_Obj b) - : Has_Block(pstate, b), media_queries_(mqs) - { statement_type(MEDIA); } - Media_Block(const Media_Block* ptr) - : Has_Block(ptr), media_queries_(ptr->media_queries_) - { statement_type(MEDIA); } - bool bubbles() { return true; } - bool is_invisible() const; - ATTACH_AST_OPERATIONS(Media_Block) - ATTACH_OPERATIONS() + ATTACH_CRTP_PERFORM_METHODS() }; /////////////////////////////////////////////////////////////////////// // At-rules -- arbitrary directives beginning with "@" that may have an // optional statement block. /////////////////////////////////////////////////////////////////////// - class Directive : public Has_Block { + class Directive final : public Has_Block { ADD_CONSTREF(std::string, keyword) - ADD_PROPERTY(Selector_List_Obj, selector) + ADD_PROPERTY(SelectorListObj, selector) ADD_PROPERTY(Expression_Obj, value) public: - Directive(ParserState pstate, std::string kwd, Selector_List_Obj sel = 0, Block_Obj b = 0, Expression_Obj val = 0) - : Has_Block(pstate, b), keyword_(kwd), selector_(sel), value_(val) // set value manually if needed - { statement_type(DIRECTIVE); } - Directive(const Directive* ptr) - : Has_Block(ptr), - keyword_(ptr->keyword_), - selector_(ptr->selector_), - value_(ptr->value_) // set value manually if needed - { statement_type(DIRECTIVE); } - bool bubbles() { return is_keyframes() || is_media(); } - bool is_media() { - return keyword_.compare("@-webkit-media") == 0 || - keyword_.compare("@-moz-media") == 0 || - keyword_.compare("@-o-media") == 0 || - keyword_.compare("@media") == 0; - } - bool is_keyframes() { - return keyword_.compare("@-webkit-keyframes") == 0 || - keyword_.compare("@-moz-keyframes") == 0 || - keyword_.compare("@-o-keyframes") == 0 || - keyword_.compare("@keyframes") == 0; - } + Directive(ParserState pstate, std::string kwd, SelectorListObj sel = {}, Block_Obj b = {}, Expression_Obj val = {}); + bool bubbles() override; + bool is_media(); + bool is_keyframes(); ATTACH_AST_OPERATIONS(Directive) - ATTACH_OPERATIONS() + ATTACH_CRTP_PERFORM_METHODS() }; /////////////////////////////////////////////////////////////////////// // Keyframe-rules -- the child blocks of "@keyframes" nodes. /////////////////////////////////////////////////////////////////////// - class Keyframe_Rule : public Has_Block { + class Keyframe_Rule final : public Has_Block { // according to css spec, this should be // = | - ADD_PROPERTY(Selector_List_Obj, name) + ADD_PROPERTY(SelectorListObj, name) public: - Keyframe_Rule(ParserState pstate, Block_Obj b) - : Has_Block(pstate, b), name_() - { statement_type(KEYFRAMERULE); } - Keyframe_Rule(const Keyframe_Rule* ptr) - : Has_Block(ptr), name_(ptr->name_) - { statement_type(KEYFRAMERULE); } + Keyframe_Rule(ParserState pstate, Block_Obj b); ATTACH_AST_OPERATIONS(Keyframe_Rule) - ATTACH_OPERATIONS() + ATTACH_CRTP_PERFORM_METHODS() }; //////////////////////////////////////////////////////////////////////// // Declarations -- style rules consisting of a property name and values. //////////////////////////////////////////////////////////////////////// - class Declaration : public Has_Block { + class Declaration final : public Has_Block { ADD_PROPERTY(String_Obj, property) ADD_PROPERTY(Expression_Obj, value) ADD_PROPERTY(bool, is_important) ADD_PROPERTY(bool, is_custom_property) ADD_PROPERTY(bool, is_indented) public: - Declaration(ParserState pstate, - String_Obj prop, Expression_Obj val, bool i = false, bool c = false, Block_Obj b = 0) - : Has_Block(pstate, b), property_(prop), value_(val), is_important_(i), is_custom_property_(c), is_indented_(false) - { statement_type(DECLARATION); } - Declaration(const Declaration* ptr) - : Has_Block(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); } - virtual bool is_invisible() const; + Declaration(ParserState pstate, String_Obj prop, Expression_Obj val, bool i = false, bool c = false, Block_Obj b = {}); + bool is_invisible() const override; ATTACH_AST_OPERATIONS(Declaration) - ATTACH_OPERATIONS() + ATTACH_CRTP_PERFORM_METHODS() }; ///////////////////////////////////// // Assignments -- variable and value. ///////////////////////////////////// - class Assignment : public Statement { + class Assignment final : public Statement { ADD_CONSTREF(std::string, variable) ADD_PROPERTY(Expression_Obj, value) ADD_PROPERTY(bool, is_default) ADD_PROPERTY(bool, is_global) public: - Assignment(ParserState pstate, - std::string var, Expression_Obj val, - bool is_default = false, - bool is_global = false) - : Statement(pstate), variable_(var), value_(val), is_default_(is_default), is_global_(is_global) - { statement_type(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); } + Assignment(ParserState pstate, std::string var, Expression_Obj val, bool is_default = false, bool is_global = false); ATTACH_AST_OPERATIONS(Assignment) - ATTACH_OPERATIONS() + 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 : public Statement { + class Import final : public Statement { std::vector urls_; - std::vector incs_; + std::vector incs_; ADD_PROPERTY(List_Obj, import_queries); public: - Import(ParserState pstate) - : Statement(pstate), - urls_(std::vector()), - incs_(std::vector()), - import_queries_() - { statement_type(IMPORT); } - Import(const Import* ptr) - : Statement(ptr), - urls_(ptr->urls_), - incs_(ptr->incs_), - import_queries_(ptr->import_queries_) - { statement_type(IMPORT); } - std::vector& urls() { return urls_; } - std::vector& incs() { return incs_; } + Import(ParserState pstate); + std::vector& incs(); + std::vector& urls(); ATTACH_AST_OPERATIONS(Import) - ATTACH_OPERATIONS() + ATTACH_CRTP_PERFORM_METHODS() }; // not yet resolved single import // so far we only know requested name - class Import_Stub : public Statement { + class Import_Stub final : public Statement { Include resource_; public: - std::string abs_path() { return resource_.abs_path; }; - std::string imp_path() { return resource_.imp_path; }; - Include resource() { return resource_; }; - - Import_Stub(ParserState pstate, Include res) - : Statement(pstate), resource_(res) - { statement_type(IMPORT_STUB); } - Import_Stub(const Import_Stub* ptr) - : Statement(ptr), resource_(ptr->resource_) - { statement_type(IMPORT_STUB); } + Import_Stub(ParserState pstate, Include res); + Include resource(); + std::string imp_path(); + std::string abs_path(); ATTACH_AST_OPERATIONS(Import_Stub) - ATTACH_OPERATIONS() + ATTACH_CRTP_PERFORM_METHODS() }; ////////////////////////////// // The Sass `@warn` directive. ////////////////////////////// - class Warning : public Statement { + class Warning final : public Statement { ADD_PROPERTY(Expression_Obj, message) public: - Warning(ParserState pstate, Expression_Obj msg) - : Statement(pstate), message_(msg) - { statement_type(WARNING); } - Warning(const Warning* ptr) - : Statement(ptr), message_(ptr->message_) - { statement_type(WARNING); } + Warning(ParserState pstate, Expression_Obj msg); ATTACH_AST_OPERATIONS(Warning) - ATTACH_OPERATIONS() + ATTACH_CRTP_PERFORM_METHODS() }; /////////////////////////////// // The Sass `@error` directive. /////////////////////////////// - class Error : public Statement { + class Error final : public Statement { ADD_PROPERTY(Expression_Obj, message) public: - Error(ParserState pstate, Expression_Obj msg) - : Statement(pstate), message_(msg) - { statement_type(ERROR); } - Error(const Error* ptr) - : Statement(ptr), message_(ptr->message_) - { statement_type(ERROR); } + Error(ParserState pstate, Expression_Obj msg); ATTACH_AST_OPERATIONS(Error) - ATTACH_OPERATIONS() + ATTACH_CRTP_PERFORM_METHODS() }; /////////////////////////////// // The Sass `@debug` directive. /////////////////////////////// - class Debug : public Statement { + class Debug final : public Statement { ADD_PROPERTY(Expression_Obj, value) public: - Debug(ParserState pstate, Expression_Obj val) - : Statement(pstate), value_(val) - { statement_type(DEBUGSTMT); } - Debug(const Debug* ptr) - : Statement(ptr), value_(ptr->value_) - { statement_type(DEBUGSTMT); } + Debug(ParserState pstate, Expression_Obj val); ATTACH_AST_OPERATIONS(Debug) - ATTACH_OPERATIONS() + ATTACH_CRTP_PERFORM_METHODS() }; /////////////////////////////////////////// // CSS comments. These may be interpolated. /////////////////////////////////////////// - class Comment : public Statement { + class Comment final : public Statement { ADD_PROPERTY(String_Obj, text) ADD_PROPERTY(bool, is_important) public: - Comment(ParserState pstate, String_Obj txt, bool is_important) - : Statement(pstate), text_(txt), is_important_(is_important) - { statement_type(COMMENT); } - Comment(const Comment* ptr) - : Statement(ptr), - text_(ptr->text_), - is_important_(ptr->is_important_) - { statement_type(COMMENT); } - virtual bool is_invisible() const - { return /* is_important() == */ false; } + Comment(ParserState pstate, String_Obj txt, bool is_important); + virtual bool is_invisible() const override; ATTACH_AST_OPERATIONS(Comment) - ATTACH_OPERATIONS() + ATTACH_CRTP_PERFORM_METHODS() }; //////////////////////////////////// // The Sass `@if` control directive. //////////////////////////////////// - class If : public Has_Block { + class If final : public Has_Block { ADD_PROPERTY(Expression_Obj, predicate) ADD_PROPERTY(Block_Obj, alternative) public: - If(ParserState pstate, Expression_Obj pred, Block_Obj con, Block_Obj alt = 0) - : Has_Block(pstate, con), predicate_(pred), alternative_(alt) - { statement_type(IF); } - If(const If* ptr) - : Has_Block(ptr), - predicate_(ptr->predicate_), - alternative_(ptr->alternative_) - { statement_type(IF); } - virtual bool has_content() - { - return Has_Block::has_content() || (alternative_ && alternative_->has_content()); - } + If(ParserState pstate, Expression_Obj pred, Block_Obj con, Block_Obj alt = {}); + virtual bool has_content() override; ATTACH_AST_OPERATIONS(If) - ATTACH_OPERATIONS() + ATTACH_CRTP_PERFORM_METHODS() }; ///////////////////////////////////// // The Sass `@for` control directive. ///////////////////////////////////// - class For : public Has_Block { + class For final : public Has_Block { ADD_CONSTREF(std::string, variable) ADD_PROPERTY(Expression_Obj, lower_bound) ADD_PROPERTY(Expression_Obj, upper_bound) ADD_PROPERTY(bool, is_inclusive) public: - For(ParserState pstate, - std::string var, Expression_Obj lo, Expression_Obj hi, Block_Obj b, bool inc) - : Has_Block(pstate, b), - variable_(var), lower_bound_(lo), upper_bound_(hi), is_inclusive_(inc) - { statement_type(FOR); } - For(const For* ptr) - : Has_Block(ptr), - variable_(ptr->variable_), - lower_bound_(ptr->lower_bound_), - upper_bound_(ptr->upper_bound_), - is_inclusive_(ptr->is_inclusive_) - { statement_type(FOR); } + For(ParserState pstate, std::string var, Expression_Obj lo, Expression_Obj hi, Block_Obj b, bool inc); ATTACH_AST_OPERATIONS(For) - ATTACH_OPERATIONS() + ATTACH_CRTP_PERFORM_METHODS() }; ////////////////////////////////////// // The Sass `@each` control directive. ////////////////////////////////////// - class Each : public Has_Block { + class Each final : public Has_Block { ADD_PROPERTY(std::vector, variables) ADD_PROPERTY(Expression_Obj, list) public: - Each(ParserState pstate, std::vector vars, Expression_Obj lst, Block_Obj b) - : Has_Block(pstate, b), variables_(vars), list_(lst) - { statement_type(EACH); } - Each(const Each* ptr) - : Has_Block(ptr), variables_(ptr->variables_), list_(ptr->list_) - { statement_type(EACH); } + Each(ParserState pstate, std::vector vars, Expression_Obj lst, Block_Obj b); ATTACH_AST_OPERATIONS(Each) - ATTACH_OPERATIONS() + ATTACH_CRTP_PERFORM_METHODS() }; /////////////////////////////////////// // The Sass `@while` control directive. /////////////////////////////////////// - class While : public Has_Block { + class While final : public Has_Block { ADD_PROPERTY(Expression_Obj, predicate) public: - While(ParserState pstate, Expression_Obj pred, Block_Obj b) - : Has_Block(pstate, b), predicate_(pred) - { statement_type(WHILE); } - While(const While* ptr) - : Has_Block(ptr), predicate_(ptr->predicate_) - { statement_type(WHILE); } + While(ParserState pstate, Expression_Obj pred, Block_Obj b); ATTACH_AST_OPERATIONS(While) - ATTACH_OPERATIONS() + ATTACH_CRTP_PERFORM_METHODS() }; ///////////////////////////////////////////////////////////// // The @return directive for use inside SassScript functions. ///////////////////////////////////////////////////////////// - class Return : public Statement { + class Return final : public Statement { ADD_PROPERTY(Expression_Obj, value) public: - Return(ParserState pstate, Expression_Obj val) - : Statement(pstate), value_(val) - { statement_type(RETURN); } - Return(const Return* ptr) - : Statement(ptr), value_(ptr->value_) - { statement_type(RETURN); } + Return(ParserState pstate, Expression_Obj val); ATTACH_AST_OPERATIONS(Return) - ATTACH_OPERATIONS() - }; - - //////////////////////////////// - // The Sass `@extend` directive. - //////////////////////////////// - class Extension : public Statement { - ADD_PROPERTY(Selector_List_Obj, selector) - public: - Extension(ParserState pstate, Selector_List_Obj s) - : Statement(pstate), selector_(s) - { statement_type(EXTEND); } - Extension(const Extension* ptr) - : Statement(ptr), selector_(ptr->selector_) - { statement_type(EXTEND); } - ATTACH_AST_OPERATIONS(Extension) - ATTACH_OPERATIONS() + ATTACH_CRTP_PERFORM_METHODS() }; ///////////////////////////////////////////////////////////////////////////// // Definitions for both mixins and functions. The two cases are distinguished // by a type tag. ///////////////////////////////////////////////////////////////////////////// - struct Backtrace; - typedef const char* Signature; - typedef Expression_Ptr (*Native_Function)(Env&, Env&, Context&, Signature, ParserState, Backtraces, std::vector); - class Definition : public Has_Block { + class Definition final : public Has_Block { public: enum Type { MIXIN, FUNCTION }; ADD_CONSTREF(std::string, name) @@ -959,439 +778,85 @@ namespace Sass { ADD_PROPERTY(bool, is_overload_stub) ADD_PROPERTY(Signature, signature) public: - Definition(const Definition* ptr) - : Has_Block(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(ParserState pstate, std::string n, Parameters_Obj params, Block_Obj b, - Type t) - : Has_Block(pstate, b), - name_(n), - parameters_(params), - environment_(0), - type_(t), - native_function_(0), - c_function_(0), - cookie_(0), - is_overload_stub_(false), - signature_(0) - { } + Type t); Definition(ParserState pstate, Signature sig, std::string n, Parameters_Obj params, Native_Function func_ptr, - bool overload_stub = false) - : Has_Block(pstate, 0), - name_(n), - parameters_(params), - environment_(0), - type_(FUNCTION), - native_function_(func_ptr), - c_function_(0), - cookie_(0), - is_overload_stub_(overload_stub), - signature_(sig) - { } + bool overload_stub = false); Definition(ParserState pstate, Signature sig, std::string n, Parameters_Obj params, - Sass_Function_Entry c_func, - bool whatever, - bool whatever2) - : Has_Block(pstate, 0), - 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) - { } + Sass_Function_Entry c_func); ATTACH_AST_OPERATIONS(Definition) - ATTACH_OPERATIONS() + ATTACH_CRTP_PERFORM_METHODS() }; ////////////////////////////////////// // Mixin calls (i.e., `@include ...`). ////////////////////////////////////// - class Mixin_Call : public Has_Block { + class Mixin_Call final : public Has_Block { ADD_CONSTREF(std::string, name) ADD_PROPERTY(Arguments_Obj, arguments) + ADD_PROPERTY(Parameters_Obj, block_parameters) public: - Mixin_Call(ParserState pstate, std::string n, Arguments_Obj args, Block_Obj b = 0) - : Has_Block(pstate, b), name_(n), arguments_(args) - { } - Mixin_Call(const Mixin_Call* ptr) - : Has_Block(ptr), - name_(ptr->name_), - arguments_(ptr->arguments_) - { } + Mixin_Call(ParserState pstate, std::string n, Arguments_Obj args, Parameters_Obj b_params = {}, Block_Obj b = {}); ATTACH_AST_OPERATIONS(Mixin_Call) - ATTACH_OPERATIONS() + ATTACH_CRTP_PERFORM_METHODS() }; /////////////////////////////////////////////////// // The @content directive for mixin content blocks. /////////////////////////////////////////////////// - class Content : public Statement { - ADD_PROPERTY(Media_Block_Ptr, media_block) + class Content final : public Statement { + ADD_PROPERTY(Arguments_Obj, arguments) public: - Content(ParserState pstate) - : Statement(pstate), - media_block_(NULL) - { statement_type(CONTENT); } - Content(const Content* ptr) - : Statement(ptr), - media_block_(ptr->media_block_) - { statement_type(CONTENT); } + Content(ParserState pstate, Arguments_Obj args); ATTACH_AST_OPERATIONS(Content) - ATTACH_OPERATIONS() - }; - - /////////////////////////////////////////////////////////////////////// - // 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(Expression_Obj e) { is_expanded(false); } - private: - ADD_PROPERTY(enum Sass_Separator, separator) - ADD_PROPERTY(bool, is_arglist) - ADD_PROPERTY(bool, is_bracketed) - ADD_PROPERTY(bool, from_selector) - public: - List(ParserState pstate, - size_t size = 0, enum Sass_Separator sep = SASS_SPACE, bool argl = false, bool bracket = false) - : Value(pstate), - Vectorized(size), - separator_(sep), - is_arglist_(argl), - is_bracketed_(bracket), - from_selector_(false) - { concrete_type(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); } - std::string type() const { return is_arglist_ ? "arglist" : "list"; } - static std::string type_name() { return "list"; } - const char* sep_string(bool compressed = false) const { - return separator() == SASS_SPACE ? - " " : (compressed ? "," : ", "); - } - bool is_invisible() const { return empty() && !is_bracketed(); } - Expression_Obj value_at_index(size_t i); - - virtual size_t size() const; - - virtual size_t hash() - { - 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()); - } - return hash_; - } - - virtual void set_delayed(bool delayed) - { - is_delayed(delayed); - // don't set children - } - - virtual bool operator== (const Expression& rhs) const; - - ATTACH_AST_OPERATIONS(List) - ATTACH_OPERATIONS() - }; - - /////////////////////////////////////////////////////////////////////// - // Key value paris. - /////////////////////////////////////////////////////////////////////// - class Map : public Value, public Hashed { - void adjust_after_pushing(std::pair p) { is_expanded(false); } - public: - Map(ParserState pstate, - size_t size = 0) - : Value(pstate), - Hashed(size) - { concrete_type(MAP); } - Map(const Map* ptr) - : Value(ptr), - Hashed(*ptr) - { concrete_type(MAP); } - std::string type() const { return "map"; } - static std::string type_name() { return "map"; } - bool is_invisible() const { return empty(); } - List_Obj to_list(ParserState& pstate); - - virtual size_t hash() - { - if (hash_ == 0) { - for (auto key : keys()) { - hash_combine(hash_, key->hash()); - hash_combine(hash_, at(key)->hash()); - } - } - - return hash_; - } - - virtual bool operator== (const Expression& rhs) const; - - ATTACH_AST_OPERATIONS(Map) - ATTACH_OPERATIONS() - }; - - inline static const std::string 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 "sub"; - 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"; - } - } - - inline static const std::string 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"; - } - } - - ////////////////////////////////////////////////////////////////////////// - // Binary expressions. Represents logical, relational, and arithmetic - // operations. Templatized to avoid large switch statements and repetitive - // subclassing. - ////////////////////////////////////////////////////////////////////////// - class Binary_Expression : public PreValue { - private: - HASH_PROPERTY(Operand, op) - HASH_PROPERTY(Expression_Obj, left) - HASH_PROPERTY(Expression_Obj, right) - size_t hash_; - public: - Binary_Expression(ParserState pstate, - Operand op, Expression_Obj lhs, Expression_Obj rhs) - : PreValue(pstate), op_(op), left_(lhs), right_(rhs), hash_(0) - { } - Binary_Expression(const Binary_Expression* ptr) - : PreValue(ptr), - op_(ptr->op_), - left_(ptr->left_), - right_(ptr->right_), - hash_(ptr->hash_) - { } - const std::string type_name() { - return sass_op_to_name(optype()); - } - const std::string separator() { - return sass_op_separator(optype()); - } - bool is_left_interpolant(void) const; - bool is_right_interpolant(void) const; - bool has_interpolant() const - { - return is_left_interpolant() || - is_right_interpolant(); - } - virtual void set_delayed(bool delayed) - { - right()->set_delayed(delayed); - left()->set_delayed(delayed); - is_delayed(delayed); - } - virtual bool operator==(const Expression& rhs) const - { - try - { - Binary_Expression_Ptr_Const m = Cast(&rhs); - if (m == 0) return false; - return type() == m->type() && - *left() == *m->left() && - *right() == *m->right(); - } - catch (std::bad_cast&) - { - return false; - } - catch (...) { throw; } - } - virtual size_t hash() - { - if (hash_ == 0) { - hash_ = std::hash()(optype()); - hash_combine(hash_, left()->hash()); - hash_combine(hash_, right()->hash()); - } - return hash_; - } - enum Sass_OP optype() const { return op_.operand; } - ATTACH_AST_OPERATIONS(Binary_Expression) - ATTACH_OPERATIONS() + ATTACH_CRTP_PERFORM_METHODS() }; //////////////////////////////////////////////////////////////////////////// // Arithmetic negation (logical negation is just an ordinary function call). //////////////////////////////////////////////////////////////////////////// - class Unary_Expression : public Expression { + class Unary_Expression final : public Expression { public: enum Type { PLUS, MINUS, NOT, SLASH }; private: HASH_PROPERTY(Type, optype) HASH_PROPERTY(Expression_Obj, operand) - size_t hash_; + mutable size_t hash_; public: - Unary_Expression(ParserState pstate, Type t, Expression_Obj o) - : Expression(pstate), optype_(t), operand_(o), hash_(0) - { } - Unary_Expression(const Unary_Expression* ptr) - : Expression(ptr), - optype_(ptr->optype_), - operand_(ptr->operand_), - hash_(ptr->hash_) - { } - const std::string type_name() { - switch (optype_) { - case PLUS: return "plus"; - case MINUS: return "minus"; - case SLASH: return "slash"; - case NOT: return "not"; - default: return "invalid"; - } - } - virtual bool operator==(const Expression& rhs) const - { - try - { - Unary_Expression_Ptr_Const m = Cast(&rhs); - if (m == 0) return false; - return type() == m->type() && - *operand() == *m->operand(); - } - catch (std::bad_cast&) - { - return false; - } - catch (...) { throw; } - } - virtual size_t hash() - { - if (hash_ == 0) { - hash_ = std::hash()(optype_); - hash_combine(hash_, operand()->hash()); - }; - return hash_; - } + Unary_Expression(ParserState pstate, Type t, Expression_Obj o); + const std::string type_name(); + virtual bool operator==(const Expression& rhs) const override; + size_t hash() const override; ATTACH_AST_OPERATIONS(Unary_Expression) - ATTACH_OPERATIONS() + ATTACH_CRTP_PERFORM_METHODS() }; //////////////////////////////////////////////////////////// // Individual argument objects for mixin and function calls. //////////////////////////////////////////////////////////// - class Argument : public Expression { + class Argument final : public Expression { HASH_PROPERTY(Expression_Obj, value) HASH_CONSTREF(std::string, name) ADD_PROPERTY(bool, is_rest_argument) ADD_PROPERTY(bool, is_keyword_argument) - size_t hash_; + mutable size_t hash_; public: - Argument(ParserState pstate, Expression_Obj val, std::string n = "", bool rest = false, bool keyword = false) - : Expression(pstate), value_(val), name_(n), is_rest_argument_(rest), is_keyword_argument_(keyword), hash_(0) - { - if (!name_.empty() && is_rest_argument_) { - coreError("variable-length argument may not be passed by name", pstate_); - } - } - 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_); - } - } - - virtual void set_delayed(bool delayed); - virtual bool operator==(const Expression& rhs) const - { - try - { - Argument_Ptr_Const m = Cast(&rhs); - if (!(m && name() == m->name())) return false; - return *value() == *m->value(); - } - catch (std::bad_cast&) - { - return false; - } - catch (...) { throw; } - } - - virtual size_t hash() - { - if (hash_ == 0) { - hash_ = std::hash()(name()); - hash_combine(hash_, value()->hash()); - } - return hash_; - } - + Argument(ParserState pstate, Expression_Obj val, std::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_OPERATIONS() + ATTACH_CRTP_PERFORM_METHODS() }; //////////////////////////////////////////////////////////////////////// @@ -1399,799 +864,174 @@ namespace Sass { // error checking (e.g., ensuring that all ordinal arguments precede all // named arguments). //////////////////////////////////////////////////////////////////////// - class Arguments : public Expression, public Vectorized { + 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); + void adjust_after_pushing(Argument_Obj a) override; public: - Arguments(ParserState pstate) - : Expression(pstate), - Vectorized(), - has_named_arguments_(false), - has_rest_argument_(false), - has_keyword_argument_(false) - { } - 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_) - { } - - virtual void set_delayed(bool delayed); - + Arguments(ParserState pstate); + void set_delayed(bool delayed) override; Argument_Obj get_rest_argument(); Argument_Obj get_keyword_argument(); - ATTACH_AST_OPERATIONS(Arguments) - ATTACH_OPERATIONS() + ATTACH_CRTP_PERFORM_METHODS() }; - //////////////////////////////////////////////////// - // Function reference. - //////////////////////////////////////////////////// - class Function : public Value { - public: - ADD_PROPERTY(Definition_Obj, definition) - ADD_PROPERTY(bool, is_css) - public: - Function(ParserState pstate, Definition_Obj def, bool css) - : Value(pstate), definition_(def), is_css_(css) - { concrete_type(FUNCTION_VAL); } - Function(const Function* ptr) - : Value(ptr), definition_(ptr->definition_), is_css_(ptr->is_css_) - { concrete_type(FUNCTION_VAL); } - - std::string type() const { return "function"; } - static std::string type_name() { return "function"; } - bool is_invisible() const { return true; } - - std::string name() { - if (definition_) { - return definition_->name(); - } - return ""; - } - virtual bool operator== (const Expression& rhs) const; + // A Media Ruleset before it has been evaluated + // Could be already final or an interpolation + class MediaRule final : public Has_Block { + ADD_PROPERTY(List_Obj, schema) + public: + MediaRule(ParserState pstate, Block_Obj block = {}); - ATTACH_AST_OPERATIONS(Function) - ATTACH_OPERATIONS() + bool bubbles() override { return true; }; + bool is_invisible() const override { return false; }; + ATTACH_AST_OPERATIONS(MediaRule) + ATTACH_CRTP_PERFORM_METHODS() }; - ////////////////// - // Function calls. - ////////////////// - class Function_Call : public PreValue { - HASH_CONSTREF(std::string, name) - HASH_PROPERTY(Arguments_Obj, arguments) - HASH_PROPERTY(Function_Obj, func) - ADD_PROPERTY(bool, via_call) - ADD_PROPERTY(void*, cookie) - size_t hash_; + // A Media Ruleset after it has been evaluated + // Representing the static or resulting css + class CssMediaRule final : public Has_Block, + public Vectorized { public: - Function_Call(ParserState pstate, std::string n, Arguments_Obj args, void* cookie) - : PreValue(pstate), name_(n), arguments_(args), func_(0), via_call_(false), cookie_(cookie), hash_(0) - { concrete_type(FUNCTION); } - Function_Call(ParserState pstate, std::string n, Arguments_Obj args, Function_Obj func) - : PreValue(pstate), name_(n), arguments_(args), func_(func), via_call_(false), cookie_(0), hash_(0) - { concrete_type(FUNCTION); } - Function_Call(ParserState pstate, std::string n, Arguments_Obj args) - : PreValue(pstate), name_(n), arguments_(args), via_call_(false), cookie_(0), hash_(0) - { concrete_type(FUNCTION); } - Function_Call(const Function_Call* ptr) - : PreValue(ptr), - name_(ptr->name_), - arguments_(ptr->arguments_), - func_(ptr->func_), - via_call_(ptr->via_call_), - cookie_(ptr->cookie_), - hash_(ptr->hash_) - { concrete_type(FUNCTION); } - - bool is_css() { - if (func_) return func_->is_css(); - return false; - } + CssMediaRule(ParserState pstate, Block_Obj b); + bool bubbles() override { return true; }; + bool isInvisible() const { return empty(); } + bool is_invisible() const override { return false; }; - virtual bool operator==(const Expression& rhs) const - { - try - { - Function_Call_Ptr_Const m = Cast(&rhs); - if (!(m && name() == m->name())) return false; - if (!(m && arguments()->length() == m->arguments()->length())) return false; - for (size_t i =0, L = arguments()->length(); i < L; ++i) - if (!(*(*arguments())[i] == *(*m->arguments())[i])) return false; - return true; - } - catch (std::bad_cast&) - { - return false; - } - catch (...) { throw; } + 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); } - - virtual size_t hash() - { - if (hash_ == 0) { - hash_ = std::hash()(name()); - for (auto argument : arguments()->elements()) - hash_combine(hash_, argument->hash()); - } - return hash_; - } - ATTACH_AST_OPERATIONS(Function_Call) - ATTACH_OPERATIONS() - }; - - ///////////////////////// - // Function call schemas. - ///////////////////////// - class Function_Call_Schema : public Expression { - ADD_PROPERTY(String_Obj, name) - ADD_PROPERTY(Arguments_Obj, arguments) - public: - Function_Call_Schema(ParserState pstate, String_Obj n, Arguments_Obj args) - : Expression(pstate), name_(n), arguments_(args) - { concrete_type(STRING); } - Function_Call_Schema(const Function_Call_Schema* ptr) - : Expression(ptr), - name_(ptr->name_), - arguments_(ptr->arguments_) - { concrete_type(STRING); } - ATTACH_AST_OPERATIONS(Function_Call_Schema) - ATTACH_OPERATIONS() - }; - - /////////////////////// - // Variable references. - /////////////////////// - class Variable : public PreValue { - ADD_CONSTREF(std::string, name) - public: - Variable(ParserState pstate, std::string n) - : PreValue(pstate), name_(n) - { concrete_type(VARIABLE); } - Variable(const Variable* ptr) - : PreValue(ptr), name_(ptr->name_) - { concrete_type(VARIABLE); } - - virtual bool operator==(const Expression& rhs) const - { - try - { - Variable_Ptr_Const e = Cast(&rhs); - return e && name() == e->name(); - } - catch (std::bad_cast&) - { - return false; - } - catch (...) { throw; } - } - - virtual size_t hash() - { - return std::hash()(name()); - } - - ATTACH_AST_OPERATIONS(Variable) - ATTACH_OPERATIONS() - }; - - //////////////////////////////////////////////// - // Numbers, percentages, dimensions, and colors. - //////////////////////////////////////////////// - class Number : public Value, public Units { - HASH_PROPERTY(double, value) - ADD_PROPERTY(bool, zero) - size_t hash_; - public: - Number(ParserState pstate, double val, std::string u = "", bool zero = true); - - Number(const Number* ptr) - : Value(ptr), - Units(ptr), - value_(ptr->value_), zero_(ptr->zero_), - hash_(ptr->hash_) - { concrete_type(NUMBER); } - - bool zero() { return zero_; } - std::string type() const { return "number"; } - static std::string type_name() { return "number"; } - - void reduce(); - void normalize(); - - virtual size_t hash() - { - 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_; - } - - virtual bool operator< (const Number& rhs) const; - virtual bool operator== (const Number& rhs) const; - virtual bool operator== (const Expression& rhs) const; - ATTACH_AST_OPERATIONS(Number) - ATTACH_OPERATIONS() - }; - - ////////// - // Colors. - ////////// - class Color : public Value { - HASH_PROPERTY(double, r) - HASH_PROPERTY(double, g) - HASH_PROPERTY(double, b) - HASH_PROPERTY(double, a) - ADD_CONSTREF(std::string, disp) - size_t hash_; - public: - Color(ParserState pstate, double r, double g, double b, double a = 1, const std::string disp = "") - : Value(pstate), r_(r), g_(g), b_(b), a_(a), disp_(disp), - hash_(0) - { concrete_type(COLOR); } - Color(const Color* ptr) - : Value(ptr), - r_(ptr->r_), - g_(ptr->g_), - b_(ptr->b_), - a_(ptr->a_), - disp_(ptr->disp_), - hash_(ptr->hash_) - { concrete_type(COLOR); } - std::string type() const { return "color"; } - static std::string type_name() { return "color"; } - - virtual size_t hash() - { - if (hash_ == 0) { - hash_ = std::hash()(a_); - hash_combine(hash_, std::hash()(r_)); - hash_combine(hash_, std::hash()(g_)); - hash_combine(hash_, std::hash()(b_)); - } - return hash_; + bool operator!=(const CssMediaRule& rhs) const { + // Invert from equality + return !(*this == rhs); } - virtual bool operator== (const Expression& rhs) const; - - ATTACH_AST_OPERATIONS(Color) - ATTACH_OPERATIONS() - }; - - ////////////////////////////// - // Errors from Sass_Values. - ////////////////////////////// - class Custom_Error : public Value { - ADD_CONSTREF(std::string, message) - public: - Custom_Error(ParserState pstate, std::string msg) - : Value(pstate), message_(msg) - { concrete_type(C_ERROR); } - Custom_Error(const Custom_Error* ptr) - : Value(ptr), message_(ptr->message_) - { concrete_type(C_ERROR); } - virtual bool operator== (const Expression& rhs) const; - ATTACH_AST_OPERATIONS(Custom_Error) - ATTACH_OPERATIONS() - }; - - ////////////////////////////// - // Warnings from Sass_Values. - ////////////////////////////// - class Custom_Warning : public Value { - ADD_CONSTREF(std::string, message) - public: - Custom_Warning(ParserState pstate, std::string msg) - : Value(pstate), message_(msg) - { concrete_type(C_WARNING); } - Custom_Warning(const Custom_Warning* ptr) - : Value(ptr), message_(ptr->message_) - { concrete_type(C_WARNING); } - virtual bool operator== (const Expression& rhs) const; - ATTACH_AST_OPERATIONS(Custom_Warning) - ATTACH_OPERATIONS() + ATTACH_AST_OPERATIONS(CssMediaRule) + ATTACH_CRTP_PERFORM_METHODS() }; - //////////// - // Booleans. - //////////// - class Boolean : public Value { - HASH_PROPERTY(bool, value) - size_t hash_; - public: - Boolean(ParserState pstate, bool val) - : Value(pstate), value_(val), - hash_(0) - { concrete_type(BOOLEAN); } - Boolean(const Boolean* ptr) - : Value(ptr), - value_(ptr->value_), - hash_(ptr->hash_) - { concrete_type(BOOLEAN); } - virtual operator bool() { return value_; } - std::string type() const { return "bool"; } - static std::string type_name() { return "bool"; } - virtual bool is_false() { return !value_; } - - virtual size_t hash() - { - if (hash_ == 0) { - hash_ = std::hash()(value_); - } - return hash_; - } + // Media Queries after they have been evaluated + // Representing the static or resulting css + class CssMediaQuery final : public AST_Node { - virtual bool operator== (const Expression& rhs) const; + // The modifier, probably either "not" or "only". + // This may be `null` if no modifier is in use. + ADD_PROPERTY(std::string, modifier); - ATTACH_AST_OPERATIONS(Boolean) - ATTACH_OPERATIONS() - }; + // The media type, for example "screen" or "print". + // This may be `null`. If so, [features] will not be empty. + ADD_PROPERTY(std::string, type); - //////////////////////////////////////////////////////////////////////// - // Abstract base class for Sass string values. Includes interpolated and - // "flat" strings. - //////////////////////////////////////////////////////////////////////// - class String : public Value { - public: - String(ParserState pstate, bool delayed = false) - : Value(pstate, delayed) - { concrete_type(STRING); } - String(const String* ptr) - : Value(ptr) - { concrete_type(STRING); } - static std::string type_name() { return "string"; } - virtual ~String() = 0; - virtual void rtrim() = 0; - virtual bool operator==(const Expression& rhs) const = 0; - virtual bool operator<(const Expression& rhs) const { - return this->to_string() < rhs.to_string(); - }; - ATTACH_VIRTUAL_AST_OPERATIONS(String); - ATTACH_OPERATIONS() - }; - inline String::~String() { }; + // Feature queries, including parentheses. + ADD_PROPERTY(std::vector, features); - /////////////////////////////////////////////////////////////////////// - // Interpolated strings. Meant to be reduced to flat strings during the - // evaluation phase. - /////////////////////////////////////////////////////////////////////// - class String_Schema : public String, public Vectorized { - ADD_PROPERTY(bool, css) - size_t hash_; public: - String_Schema(ParserState pstate, size_t size = 0, bool css = true) - : String(pstate), Vectorized(size), css_(css), hash_(0) - { concrete_type(STRING); } - String_Schema(const String_Schema* ptr) - : String(ptr), - Vectorized(*ptr), - css_(ptr->css_), - hash_(ptr->hash_) - { concrete_type(STRING); } - - std::string type() const { return "string"; } - static std::string type_name() { return "string"; } - - bool is_left_interpolant(void) const; - bool is_right_interpolant(void) const; - // void has_interpolants(bool tc) { } - bool has_interpolants() { - for (auto el : elements()) { - if (el->is_interpolant()) return true; - } - return false; - } - virtual void rtrim(); + CssMediaQuery(ParserState pstate); - virtual size_t hash() - { - if (hash_ == 0) { - for (auto string : elements()) - hash_combine(hash_, string->hash()); - } - return hash_; + // 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); } - virtual void set_delayed(bool delayed) { - is_delayed(delayed); + // Returns true if this query is empty + // Meaning it has no type and features + bool empty() const { + return type_.empty() + && modifier_.empty() + && features_.empty(); } - virtual bool operator==(const Expression& rhs) const; - ATTACH_AST_OPERATIONS(String_Schema) - ATTACH_OPERATIONS() - }; - - //////////////////////////////////////////////////////// - // Flat strings -- the lowest level of raw textual data. - //////////////////////////////////////////////////////// - class String_Constant : public String { - ADD_PROPERTY(char, quote_mark) - ADD_PROPERTY(bool, can_compress_whitespace) - HASH_CONSTREF(std::string, value) - protected: - size_t hash_; - public: - String_Constant(const String_Constant* ptr) - : String(ptr), - quote_mark_(ptr->quote_mark_), - can_compress_whitespace_(ptr->can_compress_whitespace_), - value_(ptr->value_), - hash_(ptr->hash_) - { } - String_Constant(ParserState pstate, std::string val, bool css = true) - : String(pstate), quote_mark_(0), can_compress_whitespace_(false), value_(read_css_string(val, css)), hash_(0) - { } - String_Constant(ParserState pstate, const char* beg, bool css = true) - : String(pstate), quote_mark_(0), can_compress_whitespace_(false), value_(read_css_string(std::string(beg), css)), hash_(0) - { } - String_Constant(ParserState pstate, const char* beg, const char* end, bool css = true) - : String(pstate), quote_mark_(0), can_compress_whitespace_(false), value_(read_css_string(std::string(beg, end-beg), css)), hash_(0) - { } - String_Constant(ParserState pstate, const Token& tok, bool css = true) - : String(pstate), quote_mark_(0), can_compress_whitespace_(false), value_(read_css_string(std::string(tok.begin, tok.end), css)), hash_(0) - { } - std::string type() const { return "string"; } - static std::string type_name() { return "string"; } - virtual bool is_invisible() const; - virtual void rtrim(); - - virtual size_t hash() - { - if (hash_ == 0) { - hash_ = std::hash()(value_); - } - return hash_; + // Whether this media query matches all media types. + bool matchesAllTypes() const { + return type_.empty() || Util::equalsLiteral("all", type_); } - virtual bool operator==(const Expression& rhs) const; - virtual std::string inspect() const; // quotes are forced on inspection + // 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); - // static char auto_quote() { return '*'; } - static char double_quote() { return '"'; } - static char single_quote() { return '\''; } - - ATTACH_AST_OPERATIONS(String_Constant) - ATTACH_OPERATIONS() + ATTACH_AST_OPERATIONS(CssMediaQuery) + ATTACH_CRTP_PERFORM_METHODS() }; - //////////////////////////////////////////////////////// - // Possibly quoted string (unquote on instantiation) - //////////////////////////////////////////////////////// - class String_Quoted : public String_Constant { - public: - String_Quoted(ParserState pstate, std::string val, char q = 0, - bool keep_utf8_escapes = false, bool skip_unquoting = false, - bool strict_unquoting = true, bool css = true) - : 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; - } - String_Quoted(const String_Quoted* ptr) - : String_Constant(ptr) - { } - virtual bool operator==(const Expression& rhs) const; - virtual std::string inspect() const; // quotes are forced on inspection - ATTACH_AST_OPERATIONS(String_Quoted) - ATTACH_OPERATIONS() - }; - - ///////////////// - // Media queries. - ///////////////// - class Media_Query : public Expression, - public Vectorized { + //////////////////////////////////////////////////// + // 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(ParserState pstate, - String_Obj t = 0, size_t s = 0, bool n = false, bool r = false) - : Expression(pstate), Vectorized(s), - media_type_(t), is_negated_(n), is_restricted_(r) - { } - 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(ParserState pstate, String_Obj t = {}, size_t s = 0, bool n = false, bool r = false); ATTACH_AST_OPERATIONS(Media_Query) - ATTACH_OPERATIONS() + ATTACH_CRTP_PERFORM_METHODS() }; //////////////////////////////////////////////////// // Media expressions (for use inside media queries). + // ToDo: only used for interpolation case //////////////////////////////////////////////////// - class Media_Query_Expression : public Expression { + class Media_Query_Expression final : public Expression { ADD_PROPERTY(Expression_Obj, feature) ADD_PROPERTY(Expression_Obj, value) ADD_PROPERTY(bool, is_interpolated) public: - Media_Query_Expression(ParserState pstate, - Expression_Obj f, Expression_Obj v, bool i = false) - : Expression(pstate), feature_(f), value_(v), is_interpolated_(i) - { } - Media_Query_Expression(const Media_Query_Expression* ptr) - : Expression(ptr), - feature_(ptr->feature_), - value_(ptr->value_), - is_interpolated_(ptr->is_interpolated_) - { } + Media_Query_Expression(ParserState pstate, Expression_Obj f, Expression_Obj v, bool i = false); ATTACH_AST_OPERATIONS(Media_Query_Expression) - ATTACH_OPERATIONS() - }; - - //////////////////// - // `@supports` rule. - //////////////////// - class Supports_Block : public Has_Block { - ADD_PROPERTY(Supports_Condition_Obj, condition) - public: - Supports_Block(ParserState pstate, Supports_Condition_Obj condition, Block_Obj block = 0) - : Has_Block(pstate, block), condition_(condition) - { statement_type(SUPPORTS); } - Supports_Block(const Supports_Block* ptr) - : Has_Block(ptr), condition_(ptr->condition_) - { statement_type(SUPPORTS); } - bool bubbles() { return true; } - ATTACH_AST_OPERATIONS(Supports_Block) - ATTACH_OPERATIONS() - }; - - ////////////////////////////////////////////////////// - // The abstract superclass of all Supports conditions. - ////////////////////////////////////////////////////// - class Supports_Condition : public Expression { - public: - Supports_Condition(ParserState pstate) - : Expression(pstate) - { } - Supports_Condition(const Supports_Condition* ptr) - : Expression(ptr) - { } - virtual bool needs_parens(Supports_Condition_Obj cond) const { return false; } - ATTACH_AST_OPERATIONS(Supports_Condition) - ATTACH_OPERATIONS() - }; - - //////////////////////////////////////////////////////////// - // An operator condition (e.g. `CONDITION1 and CONDITION2`). - //////////////////////////////////////////////////////////// - class Supports_Operator : public Supports_Condition { - public: - enum Operand { AND, OR }; - private: - ADD_PROPERTY(Supports_Condition_Obj, left); - ADD_PROPERTY(Supports_Condition_Obj, right); - ADD_PROPERTY(Operand, operand); - public: - Supports_Operator(ParserState pstate, Supports_Condition_Obj l, Supports_Condition_Obj r, Operand o) - : Supports_Condition(pstate), left_(l), right_(r), operand_(o) - { } - Supports_Operator(const Supports_Operator* ptr) - : Supports_Condition(ptr), - left_(ptr->left_), - right_(ptr->right_), - operand_(ptr->operand_) - { } - virtual bool needs_parens(Supports_Condition_Obj cond) const; - ATTACH_AST_OPERATIONS(Supports_Operator) - ATTACH_OPERATIONS() - }; - - ////////////////////////////////////////// - // A negation condition (`not CONDITION`). - ////////////////////////////////////////// - class Supports_Negation : public Supports_Condition { - private: - ADD_PROPERTY(Supports_Condition_Obj, condition); - public: - Supports_Negation(ParserState pstate, Supports_Condition_Obj c) - : Supports_Condition(pstate), condition_(c) - { } - Supports_Negation(const Supports_Negation* ptr) - : Supports_Condition(ptr), condition_(ptr->condition_) - { } - virtual bool needs_parens(Supports_Condition_Obj cond) const; - ATTACH_AST_OPERATIONS(Supports_Negation) - ATTACH_OPERATIONS() - }; - - ///////////////////////////////////////////////////// - // A declaration condition (e.g. `(feature: value)`). - ///////////////////////////////////////////////////// - class Supports_Declaration : public Supports_Condition { - private: - ADD_PROPERTY(Expression_Obj, feature); - ADD_PROPERTY(Expression_Obj, value); - public: - Supports_Declaration(ParserState pstate, Expression_Obj f, Expression_Obj v) - : Supports_Condition(pstate), feature_(f), value_(v) - { } - Supports_Declaration(const Supports_Declaration* ptr) - : Supports_Condition(ptr), - feature_(ptr->feature_), - value_(ptr->value_) - { } - virtual bool needs_parens(Supports_Condition_Obj cond) const { return false; } - ATTACH_AST_OPERATIONS(Supports_Declaration) - ATTACH_OPERATIONS() - }; - - /////////////////////////////////////////////// - // An interpolation condition (e.g. `#{$var}`). - /////////////////////////////////////////////// - class Supports_Interpolation : public Supports_Condition { - private: - ADD_PROPERTY(Expression_Obj, value); - public: - Supports_Interpolation(ParserState pstate, Expression_Obj v) - : Supports_Condition(pstate), value_(v) - { } - Supports_Interpolation(const Supports_Interpolation* ptr) - : Supports_Condition(ptr), - value_(ptr->value_) - { } - virtual bool needs_parens(Supports_Condition_Obj cond) const { return false; } - ATTACH_AST_OPERATIONS(Supports_Interpolation) - ATTACH_OPERATIONS() + ATTACH_CRTP_PERFORM_METHODS() }; ///////////////////////////////////////////////// // At root expressions (for use inside @at-root). ///////////////////////////////////////////////// - class At_Root_Query : public Expression { + class At_Root_Query final : public Expression { private: ADD_PROPERTY(Expression_Obj, feature) ADD_PROPERTY(Expression_Obj, value) public: - At_Root_Query(ParserState pstate, Expression_Obj f = 0, Expression_Obj v = 0, bool i = false) - : Expression(pstate), feature_(f), value_(v) - { } - At_Root_Query(const At_Root_Query* ptr) - : Expression(ptr), - feature_(ptr->feature_), - value_(ptr->value_) - { } + At_Root_Query(ParserState pstate, Expression_Obj f = {}, Expression_Obj v = {}, bool i = false); bool exclude(std::string str); ATTACH_AST_OPERATIONS(At_Root_Query) - ATTACH_OPERATIONS() + ATTACH_CRTP_PERFORM_METHODS() }; /////////// // At-root. /////////// - class At_Root_Block : public Has_Block { + class At_Root_Block final : public Has_Block { ADD_PROPERTY(At_Root_Query_Obj, expression) public: - At_Root_Block(ParserState pstate, Block_Obj b = 0, At_Root_Query_Obj e = 0) - : Has_Block(pstate, b), expression_(e) - { statement_type(ATROOT); } - At_Root_Block(const At_Root_Block* ptr) - : Has_Block(ptr), expression_(ptr->expression_) - { statement_type(ATROOT); } - bool bubbles() { return true; } - bool exclude_node(Statement_Obj s) { - if (expression() == 0) - { - return s->statement_type() == Statement::RULESET; - } - - if (s->statement_type() == Statement::DIRECTIVE) - { - if (Directive_Obj dir = Cast(s)) - { - std::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 (Directive_Obj dir = Cast(s)) - { - if (dir->is_keyframes()) return expression()->exclude("keyframes"); - } - return false; - } + At_Root_Block(ParserState pstate, Block_Obj b = {}, At_Root_Query_Obj e = {}); + bool bubbles() override; + bool exclude_node(Statement_Obj s); ATTACH_AST_OPERATIONS(At_Root_Block) - ATTACH_OPERATIONS() - }; - - ////////////////// - // The null value. - ////////////////// - class Null : public Value { - public: - Null(ParserState pstate) : Value(pstate) { concrete_type(NULL_VAL); } - Null(const Null* ptr) : Value(ptr) { concrete_type(NULL_VAL); } - std::string type() const { return "null"; } - static std::string type_name() { return "null"; } - bool is_invisible() const { return true; } - operator bool() { return false; } - bool is_false() { return true; } - - virtual size_t hash() - { - return -1; - } - - virtual bool operator== (const Expression& rhs) const; - - ATTACH_AST_OPERATIONS(Null) - ATTACH_OPERATIONS() - }; - - ///////////////////////////////// - // Thunks for delayed evaluation. - ///////////////////////////////// - class Thunk : public Expression { - ADD_PROPERTY(Expression_Obj, expression) - ADD_PROPERTY(Env*, environment) - public: - Thunk(ParserState pstate, Expression_Obj exp, Env* env = 0) - : Expression(pstate), expression_(exp), environment_(env) - { } + ATTACH_CRTP_PERFORM_METHODS() }; ///////////////////////////////////////////////////////// // Individual parameter objects for mixins and functions. ///////////////////////////////////////////////////////// - class Parameter : public AST_Node { + class Parameter final : public AST_Node { ADD_CONSTREF(std::string, name) ADD_PROPERTY(Expression_Obj, default_value) ADD_PROPERTY(bool, is_rest_parameter) public: - Parameter(ParserState pstate, - std::string n, Expression_Obj def = 0, bool rest = false) - : AST_Node(pstate), name_(n), default_value_(def), is_rest_parameter_(rest) - { - // tried to come up with a spec test for this, but it does no longer - // get past the parser (it error out earlier). A spec test was added! - // if (default_value_ && is_rest_parameter_) { - // error("variable-length parameter may not have a default value", pstate_); - // } - } - Parameter(const Parameter* ptr) - : AST_Node(ptr), - name_(ptr->name_), - default_value_(ptr->default_value_), - is_rest_parameter_(ptr->is_rest_parameter_) - { - // tried to come up with a spec test for this, but it does no longer - // get past the parser (it error out earlier). A spec test was added! - // if (default_value_ && is_rest_parameter_) { - // error("variable-length parameter may not have a default value", pstate_); - // } - } + Parameter(ParserState pstate, std::string n, Expression_Obj def = {}, bool rest = false); ATTACH_AST_OPERATIONS(Parameter) - ATTACH_OPERATIONS() + ATTACH_CRTP_PERFORM_METHODS() }; ///////////////////////////////////////////////////////////////////////// @@ -2199,850 +1039,27 @@ namespace Sass { // error checking (e.g., ensuring that all optional parameters follow all // required parameters). ///////////////////////////////////////////////////////////////////////// - class Parameters : public AST_Node, public Vectorized { + 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) - { - 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()); - } - } - } + void adjust_after_pushing(Parameter_Obj p) override; public: - Parameters(ParserState pstate) - : AST_Node(pstate), - Vectorized(), - has_optional_parameters_(false), - has_rest_parameter_(false) - { } - Parameters(const Parameters* ptr) - : AST_Node(ptr), - Vectorized(*ptr), - has_optional_parameters_(ptr->has_optional_parameters_), - has_rest_parameter_(ptr->has_rest_parameter_) - { } + Parameters(ParserState pstate); ATTACH_AST_OPERATIONS(Parameters) - ATTACH_OPERATIONS() - }; - - ///////////////////////////////////////// - // Abstract base class for CSS selectors. - ///////////////////////////////////////// - class Selector : public Expression { - // ADD_PROPERTY(bool, has_reference) - // line break before list separator - ADD_PROPERTY(bool, has_line_feed) - // line break after list separator - ADD_PROPERTY(bool, has_line_break) - // maybe we have optional flag - ADD_PROPERTY(bool, is_optional) - // parent block pointers - - // must not be a reference counted object - // otherwise we create circular references - ADD_PROPERTY(Media_Block_Ptr, media_block) - protected: - size_t hash_; - public: - Selector(ParserState pstate) - : Expression(pstate), - has_line_feed_(false), - has_line_break_(false), - is_optional_(false), - media_block_(0), - hash_(0) - { concrete_type(SELECTOR); } - Selector(const Selector* ptr) - : Expression(ptr), - // has_reference_(ptr->has_reference_), - has_line_feed_(ptr->has_line_feed_), - has_line_break_(ptr->has_line_break_), - is_optional_(ptr->is_optional_), - media_block_(ptr->media_block_), - hash_(ptr->hash_) - { concrete_type(SELECTOR); } - virtual ~Selector() = 0; - virtual size_t hash() = 0; - virtual unsigned long specificity() const = 0; - virtual void set_media_block(Media_Block_Ptr mb) { - media_block(mb); - } - virtual bool has_parent_ref() const { - return false; - } - virtual bool has_real_parent_ref() const { - return false; - } - // dispatch to correct handlers - virtual bool operator<(const Selector& rhs) const = 0; - virtual bool operator==(const Selector& rhs) const = 0; - ATTACH_VIRTUAL_AST_OPERATIONS(Selector); + ATTACH_CRTP_PERFORM_METHODS() }; - inline Selector::~Selector() { } - - ///////////////////////////////////////////////////////////////////////// - // Interpolated selectors -- the interpolated String will be expanded and - // re-parsed into a normal selector class. - ///////////////////////////////////////////////////////////////////////// - class Selector_Schema : public AST_Node { - ADD_PROPERTY(String_Obj, contents) - ADD_PROPERTY(bool, connect_parent); - // must not be a reference counted object - // otherwise we create circular references - ADD_PROPERTY(Media_Block_Ptr, media_block) - // store computed hash - size_t hash_; - public: - Selector_Schema(ParserState pstate, String_Obj c) - : AST_Node(pstate), - contents_(c), - connect_parent_(true), - media_block_(NULL), - hash_(0) - { } - Selector_Schema(const Selector_Schema* ptr) - : AST_Node(ptr), - contents_(ptr->contents_), - connect_parent_(ptr->connect_parent_), - media_block_(ptr->media_block_), - hash_(ptr->hash_) - { } - virtual bool has_parent_ref() const; - virtual bool has_real_parent_ref() const; - virtual bool operator<(const Selector& rhs) const; - virtual bool operator==(const Selector& rhs) const; - // selector schema is not yet a final selector, so we do not - // have a specificity for it yet. We need to - virtual unsigned long specificity() const { return 0; } - virtual size_t hash() { - if (hash_ == 0) { - hash_combine(hash_, contents_->hash()); - } - return hash_; - } - ATTACH_AST_OPERATIONS(Selector_Schema) - ATTACH_OPERATIONS() - }; - - //////////////////////////////////////////// - // Abstract base class for simple selectors. - //////////////////////////////////////////// - class Simple_Selector : public Selector { - ADD_CONSTREF(std::string, ns) - ADD_CONSTREF(std::string, name) - ADD_PROPERTY(Simple_Type, simple_type) - ADD_PROPERTY(bool, has_ns) - public: - Simple_Selector(ParserState pstate, std::string n = "") - : Selector(pstate), ns_(""), name_(n), has_ns_(false) - { - simple_type(SIMPLE); - size_t pos = n.find('|'); - // found some namespace - if (pos != std::string::npos) { - has_ns_ = true; - ns_ = n.substr(0, pos); - name_ = n.substr(pos + 1); - } - } - Simple_Selector(const Simple_Selector* ptr) - : Selector(ptr), - ns_(ptr->ns_), - name_(ptr->name_), - has_ns_(ptr->has_ns_) - { simple_type(SIMPLE); } - virtual std::string ns_name() const - { - std::string name(""); - if (has_ns_) - name += ns_ + "|"; - return name + name_; - } - virtual size_t hash() - { - if (hash_ == 0) { - hash_combine(hash_, std::hash()(SELECTOR)); - hash_combine(hash_, std::hash()(ns())); - hash_combine(hash_, std::hash()(name())); - } - return hash_; - } - // namespace compare functions - bool is_ns_eq(const Simple_Selector& r) const; - // namespace query functions - bool is_universal_ns() const - { - return has_ns_ && ns_ == "*"; - } - bool has_universal_ns() const - { - return !has_ns_ || ns_ == "*"; - } - bool is_empty_ns() const - { - return !has_ns_ || ns_ == ""; - } - bool has_empty_ns() const - { - return has_ns_ && ns_ == ""; - } - bool has_qualified_ns() const - { - return has_ns_ && ns_ != "" && ns_ != "*"; - } - // name query functions - bool is_universal() const - { - return name_ == "*"; - } - - virtual bool has_placeholder() { - return false; - } - - virtual ~Simple_Selector() = 0; - virtual Compound_Selector_Ptr unify_with(Compound_Selector_Ptr); - virtual bool has_parent_ref() const { return false; }; - virtual bool has_real_parent_ref() const { return false; }; - virtual bool is_pseudo_element() const { return false; } - - virtual bool is_superselector_of(Compound_Selector_Obj sub) { return false; } - - virtual bool operator==(const Selector& rhs) const; - virtual bool operator==(const Simple_Selector& rhs) const; - inline bool operator!=(const Simple_Selector& rhs) const { return !(*this == rhs); } - - bool operator<(const Selector& rhs) const; - bool operator<(const Simple_Selector& rhs) const; - // default implementation should work for most of the simple selectors (otherwise overload) - ATTACH_VIRTUAL_AST_OPERATIONS(Simple_Selector); - ATTACH_OPERATIONS(); - }; - inline Simple_Selector::~Simple_Selector() { } - - - ////////////////////////////////// - // The Parent Selector Expression. - ////////////////////////////////// - // parent selectors can occur in selectors but also - // inside strings in declarations (Compound_Selector). - // only one simple parent selector means the first case. - class Parent_Selector : public Simple_Selector { - ADD_PROPERTY(bool, real) - public: - Parent_Selector(ParserState pstate, bool r = true) - : Simple_Selector(pstate, "&"), real_(r) - { /* has_reference(true); */ } - Parent_Selector(const Parent_Selector* ptr) - : Simple_Selector(ptr), real_(ptr->real_) - { /* has_reference(true); */ } - bool is_real_parent_ref() const { return real(); }; - virtual bool has_parent_ref() const { return true; }; - virtual bool has_real_parent_ref() const { return is_real_parent_ref(); }; - virtual unsigned long specificity() const - { - return 0; - } - std::string type() const { return "selector"; } - static std::string type_name() { return "selector"; } - ATTACH_AST_OPERATIONS(Parent_Selector) - ATTACH_OPERATIONS() - }; - - ///////////////////////////////////////////////////////////////////////// - // Placeholder selectors (e.g., "%foo") for use in extend-only selectors. - ///////////////////////////////////////////////////////////////////////// - class Placeholder_Selector : public Simple_Selector { - public: - Placeholder_Selector(ParserState pstate, std::string n) - : Simple_Selector(pstate, n) - { } - Placeholder_Selector(const Placeholder_Selector* ptr) - : Simple_Selector(ptr) - { } - virtual unsigned long specificity() const - { - return Constants::Specificity_Base; - } - virtual bool has_placeholder() { - return true; - } - virtual ~Placeholder_Selector() {}; - ATTACH_AST_OPERATIONS(Placeholder_Selector) - ATTACH_OPERATIONS() - }; - - ///////////////////////////////////////////////////////////////////// - // Element selectors (and the universal selector) -- e.g., div, span, *. - ///////////////////////////////////////////////////////////////////// - class Element_Selector : public Simple_Selector { - public: - Element_Selector(ParserState pstate, std::string n) - : Simple_Selector(pstate, n) - { } - Element_Selector(const Element_Selector* ptr) - : Simple_Selector(ptr) - { } - virtual unsigned long specificity() const - { - if (name() == "*") return 0; - else return Constants::Specificity_Element; - } - virtual Simple_Selector_Ptr unify_with(Simple_Selector_Ptr); - virtual Compound_Selector_Ptr unify_with(Compound_Selector_Ptr); - virtual bool operator==(const Simple_Selector& rhs) const; - virtual bool operator==(const Element_Selector& rhs) const; - virtual bool operator<(const Simple_Selector& rhs) const; - virtual bool operator<(const Element_Selector& rhs) const; - ATTACH_AST_OPERATIONS(Element_Selector) - ATTACH_OPERATIONS() - }; - - //////////////////////////////////////////////// - // Class selectors -- i.e., .foo. - //////////////////////////////////////////////// - class Class_Selector : public Simple_Selector { - public: - Class_Selector(ParserState pstate, std::string n) - : Simple_Selector(pstate, n) - { } - Class_Selector(const Class_Selector* ptr) - : Simple_Selector(ptr) - { } - virtual unsigned long specificity() const - { - return Constants::Specificity_Class; - } - virtual Compound_Selector_Ptr unify_with(Compound_Selector_Ptr); - ATTACH_AST_OPERATIONS(Class_Selector) - ATTACH_OPERATIONS() - }; - - //////////////////////////////////////////////// - // ID selectors -- i.e., #foo. - //////////////////////////////////////////////// - class Id_Selector : public Simple_Selector { - public: - Id_Selector(ParserState pstate, std::string n) - : Simple_Selector(pstate, n) - { } - Id_Selector(const Id_Selector* ptr) - : Simple_Selector(ptr) - { } - virtual unsigned long specificity() const - { - return Constants::Specificity_ID; - } - virtual Compound_Selector_Ptr unify_with(Compound_Selector_Ptr); - ATTACH_AST_OPERATIONS(Id_Selector) - ATTACH_OPERATIONS() - }; - - /////////////////////////////////////////////////// - // Attribute selectors -- e.g., [src*=".jpg"], etc. - /////////////////////////////////////////////////// - class Attribute_Selector : public Simple_Selector { - ADD_CONSTREF(std::string, matcher) - // this cannot be changed to obj atm!!!!!!????!!!!!!! - ADD_PROPERTY(String_Obj, value) // might be interpolated - ADD_PROPERTY(char, modifier); - public: - Attribute_Selector(ParserState pstate, std::string n, std::string m, String_Obj v, char o = 0) - : Simple_Selector(pstate, n), matcher_(m), value_(v), modifier_(o) - { simple_type(ATTR_SEL); } - Attribute_Selector(const Attribute_Selector* ptr) - : Simple_Selector(ptr), - matcher_(ptr->matcher_), - value_(ptr->value_), - modifier_(ptr->modifier_) - { simple_type(ATTR_SEL); } - virtual size_t hash() - { - if (hash_ == 0) { - hash_combine(hash_, Simple_Selector::hash()); - hash_combine(hash_, std::hash()(matcher())); - if (value_) hash_combine(hash_, value_->hash()); - } - return hash_; - } - virtual unsigned long specificity() const - { - return Constants::Specificity_Attr; - } - virtual bool operator==(const Simple_Selector& rhs) const; - virtual bool operator==(const Attribute_Selector& rhs) const; - virtual bool operator<(const Simple_Selector& rhs) const; - virtual bool operator<(const Attribute_Selector& rhs) const; - ATTACH_AST_OPERATIONS(Attribute_Selector) - ATTACH_OPERATIONS() - }; - - ////////////////////////////////////////////////////////////////// - // Pseudo selectors -- e.g., :first-child, :nth-of-type(...), etc. - ////////////////////////////////////////////////////////////////// - /* '::' starts a pseudo-element, ':' a pseudo-class */ - /* Except :first-line, :first-letter, :before and :after */ - /* Note that pseudo-elements are restricted to one per selector */ - /* and occur only in the last simple_selector_sequence. */ - inline bool is_pseudo_class_element(const std::string& name) - { - return name == ":before" || - name == ":after" || - name == ":first-line" || - name == ":first-letter"; - } - - // Pseudo Selector cannot have any namespace? - class Pseudo_Selector : public Simple_Selector { - ADD_PROPERTY(String_Obj, expression) - public: - Pseudo_Selector(ParserState pstate, std::string n, String_Obj expr = 0) - : Simple_Selector(pstate, n), expression_(expr) - { simple_type(PSEUDO_SEL); } - Pseudo_Selector(const Pseudo_Selector* ptr) - : Simple_Selector(ptr), expression_(ptr->expression_) - { 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. - virtual bool is_pseudo_element() const - { - return (name_[0] == ':' && name_[1] == ':') - || is_pseudo_class_element(name_); - } - virtual size_t hash() - { - if (hash_ == 0) { - hash_combine(hash_, Simple_Selector::hash()); - if (expression_) hash_combine(hash_, expression_->hash()); - } - return hash_; - } - virtual unsigned long specificity() const - { - if (is_pseudo_element()) - return Constants::Specificity_Element; - return Constants::Specificity_Pseudo; - } - virtual bool operator==(const Simple_Selector& rhs) const; - virtual bool operator==(const Pseudo_Selector& rhs) const; - virtual bool operator<(const Simple_Selector& rhs) const; - virtual bool operator<(const Pseudo_Selector& rhs) const; - virtual Compound_Selector_Ptr unify_with(Compound_Selector_Ptr); - ATTACH_AST_OPERATIONS(Pseudo_Selector) - ATTACH_OPERATIONS() - }; - - ///////////////////////////////////////////////// - // Wrapped selector -- pseudo selector that takes a list of selectors as argument(s) e.g., :not(:first-of-type), :-moz-any(ol p.blah, ul, menu, dir) - ///////////////////////////////////////////////// - class Wrapped_Selector : public Simple_Selector { - ADD_PROPERTY(Selector_List_Obj, selector) - public: - Wrapped_Selector(ParserState pstate, std::string n, Selector_List_Obj sel) - : Simple_Selector(pstate, n), selector_(sel) - { simple_type(WRAPPED_SEL); } - Wrapped_Selector(const Wrapped_Selector* ptr) - : Simple_Selector(ptr), selector_(ptr->selector_) - { simple_type(WRAPPED_SEL); } - virtual bool is_superselector_of(Wrapped_Selector_Obj sub); - // Selectors inside the negation pseudo-class are counted like any - // other, but the negation itself does not count as a pseudo-class. - virtual size_t hash(); - virtual bool has_parent_ref() const; - virtual bool has_real_parent_ref() const; - virtual unsigned long specificity() const; - virtual bool find ( bool (*f)(AST_Node_Obj) ); - virtual bool operator==(const Simple_Selector& rhs) const; - virtual bool operator==(const Wrapped_Selector& rhs) const; - virtual bool operator<(const Simple_Selector& rhs) const; - virtual bool operator<(const Wrapped_Selector& rhs) const; - virtual void cloneChildren(); - ATTACH_AST_OPERATIONS(Wrapped_Selector) - ATTACH_OPERATIONS() - }; - - //////////////////////////////////////////////////////////////////////////// - // Simple selector sequences. Maintains flags indicating whether it contains - // any parent references or placeholders, to simplify expansion. - //////////////////////////////////////////////////////////////////////////// - class Compound_Selector : public Selector, public Vectorized { - private: - ComplexSelectorSet sources_; - ADD_PROPERTY(bool, extended); - ADD_PROPERTY(bool, has_parent_reference); - protected: - void adjust_after_pushing(Simple_Selector_Obj s) - { - // if (s->has_reference()) has_reference(true); - // if (s->has_placeholder()) has_placeholder(true); - } - public: - Compound_Selector(ParserState pstate, size_t s = 0) - : Selector(pstate), - Vectorized(s), - extended_(false), - has_parent_reference_(false) - { } - Compound_Selector(const Compound_Selector* ptr) - : Selector(ptr), - Vectorized(*ptr), - extended_(ptr->extended_), - has_parent_reference_(ptr->has_parent_reference_) - { } - bool contains_placeholder() { - for (size_t i = 0, L = length(); i < L; ++i) { - if ((*this)[i]->has_placeholder()) return true; - } - return false; - }; - - void append(Simple_Selector_Ptr element); - - bool is_universal() const - { - return length() == 1 && (*this)[0]->is_universal(); - } - - Complex_Selector_Obj to_complex(); - Compound_Selector_Ptr unify_with(Compound_Selector_Ptr rhs); - // virtual Placeholder_Selector_Ptr find_placeholder(); - virtual bool has_parent_ref() const; - virtual bool has_real_parent_ref() const; - Simple_Selector_Ptr base() const { - if (length() == 0) return 0; - // ToDo: why is this needed? - if (Cast((*this)[0])) - return (*this)[0]; - return 0; - } - virtual bool is_superselector_of(Compound_Selector_Obj sub, std::string wrapped = ""); - virtual bool is_superselector_of(Complex_Selector_Obj sub, std::string wrapped = ""); - virtual bool is_superselector_of(Selector_List_Obj sub, std::string wrapped = ""); - virtual size_t hash() - { - if (Selector::hash_ == 0) { - hash_combine(Selector::hash_, std::hash()(SELECTOR)); - if (length()) hash_combine(Selector::hash_, Vectorized::hash()); - } - return Selector::hash_; - } - virtual unsigned long specificity() const - { - int sum = 0; - for (size_t i = 0, L = length(); i < L; ++i) - { sum += (*this)[i]->specificity(); } - return sum; - } - - virtual bool has_placeholder() - { - if (length() == 0) return false; - if (Simple_Selector_Obj ss = elements().front()) { - if (ss->has_placeholder()) return true; - } - return false; - } - - bool is_empty_reference() - { - return length() == 1 && - Cast((*this)[0]); - } - - virtual bool find ( bool (*f)(AST_Node_Obj) ); - virtual bool operator<(const Selector& rhs) const; - virtual bool operator==(const Selector& rhs) const; - virtual bool operator<(const Compound_Selector& rhs) const; - virtual bool operator==(const Compound_Selector& rhs) const; - inline bool operator!=(const Compound_Selector& rhs) const { return !(*this == rhs); } - - ComplexSelectorSet& sources() { return sources_; } - void clearSources() { sources_.clear(); } - void mergeSources(ComplexSelectorSet& sources); - - Compound_Selector_Ptr minus(Compound_Selector_Ptr rhs); - virtual void cloneChildren(); - ATTACH_AST_OPERATIONS(Compound_Selector) - ATTACH_OPERATIONS() - }; - - //////////////////////////////////////////////////////////////////////////// - // General selectors -- i.e., simple sequences combined with one of the four - // CSS selector combinators (">", "+", "~", and whitespace). Essentially a - // linked list. - //////////////////////////////////////////////////////////////////////////// - class Complex_Selector : public Selector { - public: - enum Combinator { ANCESTOR_OF, PARENT_OF, PRECEDES, ADJACENT_TO, REFERENCE }; - private: - HASH_CONSTREF(Combinator, combinator) - HASH_PROPERTY(Compound_Selector_Obj, head) - HASH_PROPERTY(Complex_Selector_Obj, tail) - HASH_PROPERTY(String_Obj, reference); - public: - bool contains_placeholder() { - if (head() && head()->contains_placeholder()) return true; - if (tail() && tail()->contains_placeholder()) return true; - return false; - }; - Complex_Selector(ParserState pstate, - Combinator c = ANCESTOR_OF, - Compound_Selector_Obj h = 0, - Complex_Selector_Obj t = 0, - String_Obj r = 0) - : Selector(pstate), - combinator_(c), - head_(h), tail_(t), - reference_(r) - {} - Complex_Selector(const Complex_Selector* ptr) - : Selector(ptr), - combinator_(ptr->combinator_), - head_(ptr->head_), tail_(ptr->tail_), - reference_(ptr->reference_) - {}; - virtual bool has_parent_ref() const; - virtual bool has_real_parent_ref() const; - - Complex_Selector_Obj skip_empty_reference() - { - if ((!head_ || !head_->length() || head_->is_empty_reference()) && - combinator() == Combinator::ANCESTOR_OF) - { - if (!tail_) return 0; - tail_->has_line_feed_ = this->has_line_feed_; - // tail_->has_line_break_ = this->has_line_break_; - return tail_->skip_empty_reference(); - } - return this; - } - - // can still have a tail - bool is_empty_ancestor() const - { - return (!head() || head()->length() == 0) && - combinator() == Combinator::ANCESTOR_OF; - } - - Selector_List_Ptr tails(Selector_List_Ptr tails); - - // front returns the first real tail - // skips over parent and empty ones - Complex_Selector_Obj first(); - // last returns the last real tail - Complex_Selector_Obj last(); - - // some shortcuts that should be removed - Complex_Selector_Obj innermost() { return last(); }; - - size_t length() const; - Selector_List_Ptr resolve_parent_refs(std::vector& pstack, Backtraces& traces, bool implicit_parent = true); - virtual bool is_superselector_of(Compound_Selector_Obj sub, std::string wrapping = ""); - virtual bool is_superselector_of(Complex_Selector_Obj sub, std::string wrapping = ""); - virtual bool is_superselector_of(Selector_List_Obj sub, std::string wrapping = ""); - Selector_List_Ptr unify_with(Complex_Selector_Ptr rhs); - Combinator clear_innermost(); - void append(Complex_Selector_Obj, Backtraces& traces); - void set_innermost(Complex_Selector_Obj, Combinator); - virtual size_t hash() - { - if (hash_ == 0) { - hash_combine(hash_, std::hash()(SELECTOR)); - hash_combine(hash_, std::hash()(combinator_)); - if (head_) hash_combine(hash_, head_->hash()); - if (tail_) hash_combine(hash_, tail_->hash()); - } - return hash_; - } - virtual unsigned long specificity() const - { - int sum = 0; - if (head()) sum += head()->specificity(); - if (tail()) sum += tail()->specificity(); - return sum; - } - virtual void set_media_block(Media_Block_Ptr mb) { - media_block(mb); - if (tail_) tail_->set_media_block(mb); - if (head_) head_->set_media_block(mb); - } - virtual bool has_placeholder() { - if (head_ && head_->has_placeholder()) return true; - if (tail_ && tail_->has_placeholder()) return true; - return false; - } - virtual bool find ( bool (*f)(AST_Node_Obj) ); - virtual bool operator<(const Selector& rhs) const; - virtual bool operator==(const Selector& rhs) const; - virtual bool operator<(const Complex_Selector& rhs) const; - virtual bool operator==(const Complex_Selector& rhs) const; - inline bool operator!=(const Complex_Selector& rhs) const { return !(*this == rhs); } - const ComplexSelectorSet sources() - { - //s = Set.new - //seq.map {|sseq_or_op| s.merge sseq_or_op.sources if sseq_or_op.is_a?(SimpleSequence)} - //s - - ComplexSelectorSet srcs; - - Compound_Selector_Obj pHead = head(); - Complex_Selector_Obj pTail = tail(); - - if (pHead) { - const ComplexSelectorSet& headSources = pHead->sources(); - srcs.insert(headSources.begin(), headSources.end()); - } - - if (pTail) { - const ComplexSelectorSet& tailSources = pTail->sources(); - srcs.insert(tailSources.begin(), tailSources.end()); - } - - return srcs; - } - void addSources(ComplexSelectorSet& sources) { - // members.map! {|m| m.is_a?(SimpleSequence) ? m.with_more_sources(sources) : m} - Complex_Selector_Ptr pIter = this; - while (pIter) { - Compound_Selector_Ptr pHead = pIter->head(); - - if (pHead) { - pHead->mergeSources(sources); - } - - pIter = pIter->tail(); - } - } - void clearSources() { - Complex_Selector_Ptr pIter = this; - while (pIter) { - Compound_Selector_Ptr pHead = pIter->head(); - - if (pHead) { - pHead->clearSources(); - } - - pIter = pIter->tail(); - } - } - - virtual void cloneChildren(); - ATTACH_AST_OPERATIONS(Complex_Selector) - ATTACH_OPERATIONS() - }; - - /////////////////////////////////// - // Comma-separated selector groups. - /////////////////////////////////// - class Selector_List : public Selector, public Vectorized { - ADD_PROPERTY(Selector_Schema_Obj, schema) - ADD_CONSTREF(std::vector, wspace) - protected: - void adjust_after_pushing(Complex_Selector_Obj c); - public: - Selector_List(ParserState pstate, size_t s = 0) - : Selector(pstate), - Vectorized(s), - schema_(NULL), - wspace_(0) - { } - Selector_List(const Selector_List* ptr) - : Selector(ptr), - Vectorized(*ptr), - schema_(ptr->schema_), - wspace_(ptr->wspace_) - { } - std::string type() const { return "list"; } - // remove parent selector references - // basically unwraps parsed selectors - virtual bool has_parent_ref() const; - virtual bool has_real_parent_ref() const; - void remove_parent_selectors(); - Selector_List_Ptr resolve_parent_refs(std::vector& pstack, Backtraces& traces, bool implicit_parent = true); - virtual bool is_superselector_of(Compound_Selector_Obj sub, std::string wrapping = ""); - virtual bool is_superselector_of(Complex_Selector_Obj sub, std::string wrapping = ""); - virtual bool is_superselector_of(Selector_List_Obj sub, std::string wrapping = ""); - Selector_List_Ptr unify_with(Selector_List_Ptr); - void populate_extends(Selector_List_Obj, Subset_Map&); - Selector_List_Obj eval(Eval& eval); - virtual size_t hash() - { - if (Selector::hash_ == 0) { - hash_combine(Selector::hash_, std::hash()(SELECTOR)); - hash_combine(Selector::hash_, Vectorized::hash()); - } - return Selector::hash_; - } - virtual unsigned long specificity() const - { - unsigned long sum = 0; - unsigned long specificity; - for (size_t i = 0, L = length(); i < L; ++i) - { - specificity = (*this)[i]->specificity(); - if (sum < specificity) sum = specificity; - } - return sum; - } - virtual void set_media_block(Media_Block_Ptr mb) { - media_block(mb); - for (Complex_Selector_Obj cs : elements()) { - cs->set_media_block(mb); - } - } - virtual bool has_placeholder() { - for (Complex_Selector_Obj cs : elements()) { - if (cs->has_placeholder()) return true; - } - return false; - } - virtual bool find ( bool (*f)(AST_Node_Obj) ); - virtual bool operator<(const Selector& rhs) const; - virtual bool operator==(const Selector& rhs) const; - virtual bool operator<(const Selector_List& rhs) const; - virtual bool operator==(const Selector_List& rhs) const; - // Selector Lists can be compared to comma lists - virtual bool operator==(const Expression& rhs) const; - virtual void cloneChildren(); - ATTACH_AST_OPERATIONS(Selector_List) - ATTACH_OPERATIONS() - }; - - // compare function for sorting and probably other other uses - struct cmp_complex_selector { inline bool operator() (const Complex_Selector_Obj l, const Complex_Selector_Obj r) { return (*l < *r); } }; - struct cmp_compound_selector { inline bool operator() (const Compound_Selector_Obj l, const Compound_Selector_Obj r) { return (*l < *r); } }; - struct cmp_simple_selector { inline bool operator() (const Simple_Selector_Obj l, const Simple_Selector_Obj r) { return (*l < *r); } }; } +#include "ast_values.hpp" +#include "ast_supports.hpp" +#include "ast_selectors.hpp" + #ifdef __clang__ -#pragma clang diagnostic pop +// #pragma clang diagnostic pop +// #pragma clang diagnostic push #endif diff --git a/src/libsass/src/to_c.cpp b/src/libsass/src/ast2c.cpp similarity index 61% rename from src/libsass/src/to_c.cpp rename to src/libsass/src/ast2c.cpp index 8a6ea8d51..f167b7ea7 100644 --- a/src/libsass/src/to_c.cpp +++ b/src/libsass/src/ast2c.cpp @@ -1,28 +1,34 @@ +// sass.hpp must go before all system headers to get the +// __EXTENSIONS__ fix on Solaris. #include "sass.hpp" -#include "to_c.hpp" + +#include "ast2c.hpp" #include "ast.hpp" namespace Sass { - union Sass_Value* To_C::fallback_impl(AST_Node_Ptr n) - { return sass_make_error("unknown type for C-API"); } - - union Sass_Value* To_C::operator()(Boolean_Ptr b) + union Sass_Value* AST2C::operator()(Boolean* b) { return sass_make_boolean(b->value()); } - union Sass_Value* To_C::operator()(Number_Ptr n) + union Sass_Value* AST2C::operator()(Number* n) { return sass_make_number(n->value(), n->unit().c_str()); } - union Sass_Value* To_C::operator()(Custom_Warning_Ptr w) + union Sass_Value* AST2C::operator()(Custom_Warning* w) { return sass_make_warning(w->message().c_str()); } - union Sass_Value* To_C::operator()(Custom_Error_Ptr e) + union Sass_Value* AST2C::operator()(Custom_Error* e) { return sass_make_error(e->message().c_str()); } - union Sass_Value* To_C::operator()(Color_Ptr c) + union Sass_Value* AST2C::operator()(Color_RGBA* c) { return sass_make_color(c->r(), c->g(), c->b(), c->a()); } - union Sass_Value* To_C::operator()(String_Constant_Ptr s) + 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()); @@ -31,10 +37,10 @@ namespace Sass { } } - union Sass_Value* To_C::operator()(String_Quoted_Ptr s) + union Sass_Value* AST2C::operator()(String_Quoted* s) { return sass_make_qstring(s->value().c_str()); } - union Sass_Value* To_C::operator()(List_Ptr l) + 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) { @@ -43,7 +49,7 @@ namespace Sass { return v; } - union Sass_Value* To_C::operator()(Map_Ptr m) + union Sass_Value* AST2C::operator()(Map* m) { union Sass_Value* v = sass_make_map(m->length()); int i = 0; @@ -55,7 +61,7 @@ namespace Sass { return v; } - union Sass_Value* To_C::operator()(Arguments_Ptr a) + 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) { @@ -64,11 +70,11 @@ namespace Sass { return v; } - union Sass_Value* To_C::operator()(Argument_Ptr a) + union Sass_Value* AST2C::operator()(Argument* a) { return a->value()->perform(this); } // not strictly necessary because of the fallback - union Sass_Value* To_C::operator()(Null_Ptr n) + union Sass_Value* AST2C::operator()(Null* n) { return sass_make_null(); } }; diff --git a/src/libsass/src/ast2c.hpp b/src/libsass/src/ast2c.hpp new file mode 100644 index 000000000..cd99a17c3 --- /dev/null +++ b/src/libsass/src/ast2c.hpp @@ -0,0 +1,39 @@ +#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/libsass/src/ast_def_macros.hpp b/src/libsass/src/ast_def_macros.hpp index b3a7f8d16..3a0b050f6 100644 --- a/src/libsass/src/ast_def_macros.hpp +++ b/src/libsass/src/ast_def_macros.hpp @@ -35,16 +35,6 @@ class LocalOption { LocalOption cnt_##name(name, name + 1); \ if (name > MAX_NESTING) throw Exception::NestingLimitError(pstate, traces); \ -#define ATTACH_OPERATIONS()\ -virtual void perform(Operation* op) { (*op)(this); }\ -virtual AST_Node_Ptr perform(Operation* op) { return (*op)(this); }\ -virtual Statement_Ptr perform(Operation* op) { return (*op)(this); }\ -virtual Expression_Ptr perform(Operation* op) { return (*op)(this); }\ -virtual Selector_Ptr perform(Operation* op) { return (*op)(this); }\ -virtual std::string perform(Operation* op) { return (*op)(this); }\ -virtual union Sass_Value* perform(Operation* op) { return (*op)(this); }\ -virtual Value_Ptr perform(Operation* op) { return (*op)(this); } - #define ADD_PROPERTY(type, name)\ protected:\ type name##_;\ @@ -77,4 +67,74 @@ public: \ void name(type name##__) { hash_ = 0; name##_ = name##__; } \ private: +#ifdef DEBUG_SHARED_PTR + +#define ATTACH_ABSTRACT_AST_OPERATIONS(klass) \ + virtual klass* copy(std::string, size_t) const = 0; \ + virtual klass* clone(std::string, size_t) const = 0; \ + +#define ATTACH_VIRTUAL_AST_OPERATIONS(klass) \ + klass(const klass* ptr); \ + virtual klass* copy(std::string, size_t) const override = 0; \ + virtual klass* clone(std::string, size_t) const override = 0; \ + +#define ATTACH_AST_OPERATIONS(klass) \ + klass(const klass* ptr); \ + virtual klass* copy(std::string, size_t) const override; \ + virtual klass* clone(std::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 IMPLEMENT_AST_OPERATORS(klass) \ + klass* klass::copy(std::string file, size_t line) const { \ + klass* cpy = new klass(this); \ + cpy->trace(file, line); \ + return cpy; \ + } \ + klass* klass::clone(std::string file, size_t line) const { \ + klass* cpy = copy(file, line); \ + cpy->cloneChildren(); \ + return cpy; \ + } \ + +#else + + #define IMPLEMENT_AST_OPERATORS(klass) \ + klass* klass::copy() const { \ + return new klass(this); \ + } \ + klass* klass::clone() const { \ + klass* cpy = copy(); \ + cpy->cloneChildren(); \ + return cpy; \ + } \ + +#endif + #endif diff --git a/src/libsass/src/ast_fwd_decl.cpp b/src/libsass/src/ast_fwd_decl.cpp index c9c76727a..814d3f662 100644 --- a/src/libsass/src/ast_fwd_decl.cpp +++ b/src/libsass/src/ast_fwd_decl.cpp @@ -19,11 +19,13 @@ namespace Sass { IMPLEMENT_BASE_CAST(Has_Block) 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(Supports_Condition) IMPLEMENT_BASE_CAST(Selector) - IMPLEMENT_BASE_CAST(Simple_Selector) + IMPLEMENT_BASE_CAST(SelectorComponent) + IMPLEMENT_BASE_CAST(SimpleSelector) } diff --git a/src/libsass/src/ast_fwd_decl.hpp b/src/libsass/src/ast_fwd_decl.hpp index 5145a092b..4c7b6f5af 100644 --- a/src/libsass/src/ast_fwd_decl.hpp +++ b/src/libsass/src/ast_fwd_decl.hpp @@ -1,17 +1,12 @@ #ifndef SASS_AST_FWD_DECL_H #define SASS_AST_FWD_DECL_H -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "memory/SharedPtr.hpp" +// sass.hpp must go before all system headers to get the +// __EXTENSIONS__ fix on Solaris. +#include "sass.hpp" + #include "sass/functions.h" +#include "memory/SharedPtr.hpp" ///////////////////////////////////////////// // Forward declarations for the AST visitors. @@ -19,266 +14,119 @@ namespace Sass { class AST_Node; - typedef AST_Node* AST_Node_Ptr; - typedef AST_Node const* AST_Node_Ptr_Const; class Has_Block; - typedef Has_Block* Has_Block_Ptr; - typedef Has_Block const* Has_Block_Ptr_Const; - class Simple_Selector; - typedef Simple_Selector* Simple_Selector_Ptr; - typedef Simple_Selector const* Simple_Selector_Ptr_Const; + class SimpleSelector; + + class Parent_Reference; class PreValue; - typedef PreValue* PreValue_Ptr; - typedef PreValue const* PreValue_Ptr_Const; - class Thunk; - typedef Thunk* Thunk_Ptr; - typedef Thunk const* Thunk_Ptr_Const; class Block; - typedef Block* Block_Ptr; - typedef Block const* Block_Ptr_Const; class Expression; - typedef Expression* Expression_Ptr; - typedef Expression const* Expression_Ptr_Const; class Statement; - typedef Statement* Statement_Ptr; - typedef Statement const* Statement_Ptr_Const; class Value; - typedef Value* Value_Ptr; - typedef Value const* Value_Ptr_Const; class Declaration; - typedef Declaration* Declaration_Ptr; - typedef Declaration const* Declaration_Ptr_Const; class Ruleset; - typedef Ruleset* Ruleset_Ptr; - typedef Ruleset const* Ruleset_Ptr_Const; class Bubble; - typedef Bubble* Bubble_Ptr; - typedef Bubble const* Bubble_Ptr_Const; class Trace; - typedef Trace* Trace_Ptr; - typedef Trace const* Trace_Ptr_Const; - class Media_Block; - typedef Media_Block* Media_Block_Ptr; - typedef Media_Block const* Media_Block_Ptr_Const; + class MediaRule; + class CssMediaRule; + class CssMediaQuery; + class Supports_Block; - typedef Supports_Block* Supports_Block_Ptr; - typedef Supports_Block const* Supports_Block_Ptr_Const; class Directive; - typedef Directive* Directive_Ptr; - typedef Directive const* Directive_Ptr_Const; - class Keyframe_Rule; - typedef Keyframe_Rule* Keyframe_Rule_Ptr; - typedef Keyframe_Rule const* Keyframe_Rule_Ptr_Const; class At_Root_Block; - typedef At_Root_Block* At_Root_Block_Ptr; - typedef At_Root_Block const* At_Root_Block_Ptr_Const; class Assignment; - typedef Assignment* Assignment_Ptr; - typedef Assignment const* Assignment_Ptr_Const; class Import; - typedef Import* Import_Ptr; - typedef Import const* Import_Ptr_Const; class Import_Stub; - typedef Import_Stub* Import_Stub_Ptr; - typedef Import_Stub const* Import_Stub_Ptr_Const; class Warning; - typedef Warning* Warning_Ptr; - typedef Warning const* Warning_Ptr_Const; class Error; - typedef Error* Error_Ptr; - typedef Error const* Error_Ptr_Const; class Debug; - typedef Debug* Debug_Ptr; - typedef Debug const* Debug_Ptr_Const; class Comment; - typedef Comment* Comment_Ptr; - typedef Comment const* Comment_Ptr_Const; class If; - typedef If* If_Ptr; - typedef If const* If_Ptr_Const; class For; - typedef For* For_Ptr; - typedef For const* For_Ptr_Const; class Each; - typedef Each* Each_Ptr; - typedef Each const* Each_Ptr_Const; class While; - typedef While* While_Ptr; - typedef While const* While_Ptr_Const; class Return; - typedef Return* Return_Ptr; - typedef Return const* Return_Ptr_Const; class Content; - typedef Content* Content_Ptr; - typedef Content const* Content_Ptr_Const; - class Extension; - typedef Extension* Extension_Ptr; - typedef Extension const* Extension_Ptr_Const; + class ExtendRule; class Definition; - typedef Definition* Definition_Ptr; - typedef Definition const* Definition_Ptr_Const; class List; - typedef List* List_Ptr; - typedef List const* List_Ptr_Const; class Map; - typedef Map* Map_Ptr; - typedef Map const* Map_Ptr_Const; class Function; - typedef Function* Function_Ptr; - typedef Function const* Function_Ptr_Const; class Mixin_Call; - typedef Mixin_Call* Mixin_Call_Ptr; - typedef Mixin_Call const* Mixin_Call_Ptr_Const; class Binary_Expression; - typedef Binary_Expression* Binary_Expression_Ptr; - typedef Binary_Expression const* Binary_Expression_Ptr_Const; class Unary_Expression; - typedef Unary_Expression* Unary_Expression_Ptr; - typedef Unary_Expression const* Unary_Expression_Ptr_Const; class Function_Call; - typedef Function_Call* Function_Call_Ptr; - typedef Function_Call const* Function_Call_Ptr_Const; - class Function_Call_Schema; - typedef Function_Call_Schema* Function_Call_Schema_Ptr; - typedef Function_Call_Schema const* Function_Call_Schema_Ptr_Const; class Custom_Warning; - typedef Custom_Warning* Custom_Warning_Ptr; - typedef Custom_Warning const* Custom_Warning_Ptr_Const; class Custom_Error; - typedef Custom_Error* Custom_Error_Ptr; - typedef Custom_Error const* Custom_Error_Ptr_Const; class Variable; - typedef Variable* Variable_Ptr; - typedef Variable const* Variable_Ptr_Const; class Number; - typedef Number* Number_Ptr; - typedef Number const* Number_Ptr_Const; class Color; - typedef Color* Color_Ptr; - typedef Color const* Color_Ptr_Const; + class Color_RGBA; + class Color_HSLA; class Boolean; - typedef Boolean* Boolean_Ptr; - typedef Boolean const* Boolean_Ptr_Const; class String; - typedef String* String_Ptr; - typedef String const* String_Ptr_Const; + class Null; class String_Schema; - typedef String_Schema* String_Schema_Ptr; - typedef String_Schema const* String_Schema_Ptr_Const; class String_Constant; - typedef String_Constant* String_Constant_Ptr; - typedef String_Constant const* String_Constant_Ptr_Const; class String_Quoted; - typedef String_Quoted* String_Quoted_Ptr; - typedef String_Quoted const* String_Quoted_Ptr_Const; class Media_Query; - typedef Media_Query* Media_Query_Ptr; - typedef Media_Query const* Media_Query_Ptr_Const; class Media_Query_Expression; - typedef Media_Query_Expression* Media_Query_Expression_Ptr; - typedef Media_Query_Expression const* Media_Query_Expression_Ptr_Const; class Supports_Condition; - typedef Supports_Condition* Supports_Condition_Ptr; - typedef Supports_Condition const* Supports_Condition_Ptr_Const; class Supports_Operator; - typedef Supports_Operator* Supports_Operator_Ptr; - typedef Supports_Operator const* Supports_Operator_Ptr_Const; class Supports_Negation; - typedef Supports_Negation* Supports_Negation_Ptr; - typedef Supports_Negation const* Supports_Negation_Ptr_Const; class Supports_Declaration; - typedef Supports_Declaration* Supports_Declaration_Ptr; - typedef Supports_Declaration const* Supports_Declaration_Ptr_Const; class Supports_Interpolation; - typedef Supports_Interpolation* Supports_Interpolation_Ptr; - typedef Supports_Interpolation const* Supports_Interpolation_Ptr_Const; - - - class Null; - typedef Null* Null_Ptr; - typedef Null const* Null_Ptr_Const; - + class At_Root_Query; - typedef At_Root_Query* At_Root_Query_Ptr; - typedef At_Root_Query const* At_Root_Query_Ptr_Const; - class Parent_Selector; - typedef Parent_Selector* Parent_Selector_Ptr; - typedef Parent_Selector const* Parent_Selector_Ptr_Const; class Parameter; - typedef Parameter* Parameter_Ptr; - typedef Parameter const* Parameter_Ptr_Const; class Parameters; - typedef Parameters* Parameters_Ptr; - typedef Parameters const* Parameters_Ptr_Const; class Argument; - typedef Argument* Argument_Ptr; - typedef Argument const* Argument_Ptr_Const; class Arguments; - typedef Arguments* Arguments_Ptr; - typedef Arguments const* Arguments_Ptr_Const; class Selector; - typedef Selector* Selector_Ptr; - typedef Selector const* Selector_Ptr_Const; class Selector_Schema; - typedef Selector_Schema* Selector_Schema_Ptr; - typedef Selector_Schema const* Selector_Schema_Ptr_Const; class Placeholder_Selector; - typedef Placeholder_Selector* Placeholder_Selector_Ptr; - typedef Placeholder_Selector const* Placeholder_Selector_Ptr_Const; - class Element_Selector; - typedef Element_Selector* Element_Selector_Ptr; - typedef Element_Selector const* Element_Selector_Ptr_Const; + class Type_Selector; class Class_Selector; - typedef Class_Selector* Class_Selector_Ptr; - typedef Class_Selector const* Class_Selector_Ptr_Const; class Id_Selector; - typedef Id_Selector* Id_Selector_Ptr; - typedef Id_Selector const* Id_Selector_Ptr_Const; class Attribute_Selector; - typedef Attribute_Selector* Attribute_Selector_Ptr; - typedef Attribute_Selector const* Attribute_Selector_Ptr_Const; class Pseudo_Selector; - typedef Pseudo_Selector* Pseudo_Selector_Ptr; - typedef Pseudo_Selector const * Pseudo_Selector_Ptr_Const; - class Wrapped_Selector; - typedef Wrapped_Selector* Wrapped_Selector_Ptr; - typedef Wrapped_Selector const * Wrapped_Selector_Ptr_Const; - class Compound_Selector; - typedef Compound_Selector* Compound_Selector_Ptr; - typedef Compound_Selector const * Compound_Selector_Ptr_Const; - class Complex_Selector; - typedef Complex_Selector* Complex_Selector_Ptr; - typedef Complex_Selector const * Complex_Selector_Ptr_Const; - class Selector_List; - typedef Selector_List* Selector_List_Ptr; - typedef Selector_List const * Selector_List_Ptr_Const; - + + class SelectorComponent; + class SelectorCombinator; + class CompoundSelector; + class ComplexSelector; + class SelectorList; // common classes class Context; class Expand; class Eval; + class Extension; + // declare classes that are instances of memory nodes - // #define IMPL_MEM_OBJ(type) using type##_Obj = SharedImpl - #define IMPL_MEM_OBJ(type) typedef SharedImpl type##_Obj + // Note: also add a mapping without underscore + // ToDo: move to camelCase vars in the future + #define IMPL_MEM_OBJ(type) \ + typedef SharedImpl type##Obj; \ + typedef SharedImpl type##_Obj; \ IMPL_MEM_OBJ(AST_Node); IMPL_MEM_OBJ(Statement); @@ -286,7 +134,9 @@ namespace Sass { IMPL_MEM_OBJ(Ruleset); IMPL_MEM_OBJ(Bubble); IMPL_MEM_OBJ(Trace); - IMPL_MEM_OBJ(Media_Block); + IMPL_MEM_OBJ(MediaRule); + IMPL_MEM_OBJ(CssMediaRule); + IMPL_MEM_OBJ(CssMediaQuery); IMPL_MEM_OBJ(Supports_Block); IMPL_MEM_OBJ(Directive); IMPL_MEM_OBJ(Keyframe_Rule); @@ -301,14 +151,13 @@ namespace Sass { IMPL_MEM_OBJ(Comment); IMPL_MEM_OBJ(PreValue); IMPL_MEM_OBJ(Has_Block); - IMPL_MEM_OBJ(Thunk); IMPL_MEM_OBJ(If); IMPL_MEM_OBJ(For); IMPL_MEM_OBJ(Each); IMPL_MEM_OBJ(While); IMPL_MEM_OBJ(Return); IMPL_MEM_OBJ(Content); - IMPL_MEM_OBJ(Extension); + IMPL_MEM_OBJ(ExtendRule); IMPL_MEM_OBJ(Definition); IMPL_MEM_OBJ(Mixin_Call); IMPL_MEM_OBJ(Value); @@ -319,12 +168,13 @@ namespace Sass { IMPL_MEM_OBJ(Binary_Expression); IMPL_MEM_OBJ(Unary_Expression); IMPL_MEM_OBJ(Function_Call); - IMPL_MEM_OBJ(Function_Call_Schema); IMPL_MEM_OBJ(Custom_Warning); IMPL_MEM_OBJ(Custom_Error); IMPL_MEM_OBJ(Variable); IMPL_MEM_OBJ(Number); IMPL_MEM_OBJ(Color); + IMPL_MEM_OBJ(Color_RGBA); + IMPL_MEM_OBJ(Color_HSLA); IMPL_MEM_OBJ(Boolean); IMPL_MEM_OBJ(String_Schema); IMPL_MEM_OBJ(String); @@ -339,87 +189,37 @@ namespace Sass { IMPL_MEM_OBJ(Supports_Interpolation); IMPL_MEM_OBJ(At_Root_Query); IMPL_MEM_OBJ(Null); - IMPL_MEM_OBJ(Parent_Selector); + IMPL_MEM_OBJ(Parent_Reference); IMPL_MEM_OBJ(Parameter); IMPL_MEM_OBJ(Parameters); IMPL_MEM_OBJ(Argument); IMPL_MEM_OBJ(Arguments); IMPL_MEM_OBJ(Selector); IMPL_MEM_OBJ(Selector_Schema); - IMPL_MEM_OBJ(Simple_Selector); + IMPL_MEM_OBJ(SimpleSelector); IMPL_MEM_OBJ(Placeholder_Selector); - IMPL_MEM_OBJ(Element_Selector); + IMPL_MEM_OBJ(Type_Selector); IMPL_MEM_OBJ(Class_Selector); IMPL_MEM_OBJ(Id_Selector); IMPL_MEM_OBJ(Attribute_Selector); IMPL_MEM_OBJ(Pseudo_Selector); - IMPL_MEM_OBJ(Wrapped_Selector); - IMPL_MEM_OBJ(Compound_Selector); - IMPL_MEM_OBJ(Complex_Selector); - IMPL_MEM_OBJ(Selector_List); - - // ########################################################################### - // Implement compare, order and hashing operations for AST Nodes - // ########################################################################### - struct HashNodes { - template - size_t operator() (const T& ex) const { - return ex.isNull() ? 0 : ex->hash(); - } - }; - struct OrderNodes { - template - bool operator() (const T& lhs, const T& rhs) const { - return !lhs.isNull() && !rhs.isNull() && *lhs < *rhs; - } - }; - struct CompareNodes { - template - bool operator() (const T& lhs, const T& rhs) const { - // code around sass logic issue. 1px == 1 is true - // but both items are still different keys in maps - if (dynamic_cast(lhs.ptr())) - if (dynamic_cast(rhs.ptr())) - return lhs->hash() == rhs->hash(); - return !lhs.isNull() && !rhs.isNull() && *lhs == *rhs; - } - }; + IMPL_MEM_OBJ(SelectorComponent); + IMPL_MEM_OBJ(SelectorCombinator); + IMPL_MEM_OBJ(CompoundSelector); + IMPL_MEM_OBJ(ComplexSelector); + IMPL_MEM_OBJ(SelectorList); // ########################################################################### // some often used typedefs // ########################################################################### - typedef std::unordered_map< - Expression_Obj, // key - Expression_Obj, // value - HashNodes, // hasher - CompareNodes // compare - > ExpressionMap; - typedef std::unordered_set< - Expression_Obj, // value - HashNodes, // hasher - CompareNodes // compare - > ExpressionSet; - - typedef std::string SubSetMapKey; - typedef std::vector SubSetMapKeys; - - typedef std::pair SubSetMapPair; - typedef std::pair SubSetMapLookup; - typedef std::vector SubSetMapPairs; - typedef std::vector SubSetMapLookups; - - typedef std::pair SubSetMapResult; - typedef std::vector SubSetMapResults; - - typedef std::deque ComplexSelectorDeque; - typedef std::set SimpleSelectorSet; - typedef std::set ComplexSelectorSet; - typedef std::set CompoundSelectorSet; - typedef std::unordered_set SimpleSelectorDict; - - typedef std::vector* ImporterStack; + typedef std::vector BlockStack; + typedef std::vector CalleeStack; + typedef std::vector CallStack; + typedef std::vector MediaStack; + typedef std::vector SelectorStack; + typedef std::vector ImporterStack; // only to switch implementations for testing #define environment_map std::map @@ -452,11 +252,13 @@ namespace Sass { DECLARE_BASE_CAST(PreValue) DECLARE_BASE_CAST(Value) DECLARE_BASE_CAST(List) + DECLARE_BASE_CAST(Color) DECLARE_BASE_CAST(String) DECLARE_BASE_CAST(String_Constant) DECLARE_BASE_CAST(Supports_Condition) DECLARE_BASE_CAST(Selector) - DECLARE_BASE_CAST(Simple_Selector) + DECLARE_BASE_CAST(SimpleSelector) + DECLARE_BASE_CAST(SelectorComponent) } diff --git a/src/libsass/src/ast_helpers.hpp b/src/libsass/src/ast_helpers.hpp new file mode 100644 index 000000000..4f7efd430 --- /dev/null +++ b/src/libsass/src/ast_helpers.hpp @@ -0,0 +1,292 @@ +#ifndef SASS_AST_HELPERS_H +#define SASS_AST_HELPERS_H + +// sass.hpp must go before all system headers to get the +// __EXTENSIONS__ fix on Solaris. +#include "sass.hpp" +#include +#include +#include "util_string.hpp" + +namespace Sass { + + // ########################################################################### + // ########################################################################### + + // easier to search with name + const bool DELAYED = true; + + // ToDo: should this really be hardcoded + // Note: most methods follow precision option + const double NUMBER_EPSILON = 1e-12; + + // macro to test if numbers are equal within a small error margin + #define NEAR_EQUAL(lhs, rhs) std::fabs(lhs - rhs) < NUMBER_EPSILON + + // ########################################################################### + // We define various functions and functors here. + // Functions satisfy the BinaryPredicate requirement + // Functors are structs used for e.g. unordered_map + // ########################################################################### + + // ########################################################################### + // Implement compare and hashing operations for raw pointers + // ########################################################################### + + template + size_t PtrHashFn(const T* ptr) { + return std::hash()((size_t)ptr); + } + + struct PtrHash { + template + size_t operator() (const T* ptr) const { + return PtrHashFn(ptr); + } + }; + + template + bool PtrEqualityFn(const T* lhs, const T* rhs) { + return lhs == rhs; // compare raw pointers + } + + struct PtrEquality { + template + bool operator() (const T* lhs, const T* rhs) const { + return PtrEqualityFn(lhs, rhs); + } + }; + + // ########################################################################### + // Implement compare and hashing operations for AST Nodes + // ########################################################################### + + // TODO: get rid of funtions and use ObjEquality + + template + // Hash the raw pointer instead of object + size_t ObjPtrHashFn(const T& obj) { + return PtrHashFn(obj.ptr()); + } + + struct ObjPtrHash { + template + // Hash the raw pointer instead of object + size_t operator() (const T& obj) const { + return ObjPtrHashFn(obj); + } + }; + + template + // Hash the object and its content + size_t ObjHashFn(const T& obj) { + return obj ? obj->hash() : 0; + } + + struct ObjHash { + template + // Hash the object and its content + size_t operator() (const T& obj) const { + return ObjHashFn(obj); + } + }; + + template + // Hash the object behind pointer + size_t PtrObjHashFn(const T* obj) { + return obj ? obj->hash() : 0; + } + + struct PtrObjHash { + template + // Hash the object behind pointer + size_t operator() (const T* obj) const { + return PtrObjHashFn(obj); + } + }; + + template + // Compare raw pointers to the object + bool ObjPtrEqualityFn(const T& lhs, const T& rhs) { + return PtrEqualityFn(lhs.ptr(), rhs.ptr()); + } + + struct ObjPtrEquality { + template + // Compare raw pointers to the object + bool operator() (const T& lhs, const T& rhs) const { + return ObjPtrEqualityFn(lhs, rhs); + } + }; + + template + // Compare the objects behind the pointers + bool PtrObjEqualityFn(const T* lhs, const T* rhs) { + if (lhs == nullptr) return rhs == nullptr; + else if (rhs == nullptr) return false; + else return *lhs == *rhs; + } + + struct PtrObjEquality { + template + // Compare the objects behind the pointers + bool operator() (const T* lhs, const T* rhs) const { + return PtrObjEqualityFn(lhs, rhs); + } + }; + + template + // Compare the objects and its contents + bool ObjEqualityFn(const T& lhs, const T& rhs) { + return PtrObjEqualityFn(lhs.ptr(), rhs.ptr()); + } + + struct ObjEquality { + template + // Compare the objects and its contents + bool operator() (const T& lhs, const T& rhs) const { + return ObjEqualityFn(lhs, rhs); + } + }; + + // ########################################################################### + // Implement ordering operations for AST Nodes + // ########################################################################### + + template + // Compare the objects behind pointers + bool PtrObjLessThanFn(const T* lhs, const T* rhs) { + if (lhs == nullptr) return rhs != nullptr; + else if (rhs == nullptr) return false; + else return *lhs < *rhs; + } + + struct PtrObjLessThan { + template + // Compare the objects behind pointers + bool operator() (const T* lhs, const T* rhs) const { + return PtrObjLessThanFn(lhs, rhs); + } + }; + + template + // Compare the objects and its content + bool ObjLessThanFn(const T& lhs, const T& rhs) { + return PtrObjLessThanFn(lhs.ptr(), rhs.ptr()); + }; + + struct ObjLessThan { + template + // Compare the objects and its content + bool operator() (const T& lhs, const T& rhs) const { + return ObjLessThanFn(lhs, rhs); + } + }; + + // ########################################################################### + // Some STL helper functions + // ########################################################################### + + // Check if all elements are equal + template + bool ListEquality(const X& lhs, const Y& rhs, + bool(*cmp)(const XT*, const YT*)) + { + return lhs.size() == rhs.size() && + std::equal(lhs.begin(), lhs.end(), + rhs.begin(), cmp); + } + + // Return if Vector is empty + template + bool listIsEmpty(T* cnt) { + return cnt && cnt->empty(); + } + + // Erase items from vector that match predicate + template + void listEraseItemIf(T& vec, UnaryPredicate* predicate) + { + vec.erase(std::remove_if(vec.begin(), vec.end(), predicate), vec.end()); + } + + // Check that every item in `lhs` is also in `rhs` + // Note: this works by comparing the raw pointers + template + bool listIsSubsetOrEqual(const T& lhs, const T& rhs) { + for (const auto& item : lhs) { + if (std::find(rhs.begin(), rhs.end(), item) == rhs.end()) + return false; + } + return true; + } + + // ########################################################################## + // Returns whether [name] is the name of a pseudo-element + // that can be written with pseudo-class syntax (CSS2 vs CSS3): + // `:before`, `:after`, `:first-line`, or `:first-letter` + // ########################################################################## + inline bool isFakePseudoElement(const std::string& name) + { + return Util::equalsLiteral("after", name) + || Util::equalsLiteral("before", name) + || Util::equalsLiteral("first-line", name) + || Util::equalsLiteral("first-letter", name); + } + + // ########################################################################## + // Names of pseudo selectors that take selectors as arguments, + // and that are subselectors of their arguments. + // For example, `.foo` is a superselector of `:matches(.foo)`. + // ########################################################################## + inline bool isSubselectorPseudo(const std::string& norm) + { + return Util::equalsLiteral("any", norm) + || Util::equalsLiteral("matches", norm) + || Util::equalsLiteral("nth-child", norm) + || Util::equalsLiteral("nth-last-child", norm); + } + // EO isSubselectorPseudo + + // ########################################################################### + // Pseudo-class selectors that take unadorned selectors as arguments. + // ########################################################################### + inline bool isSelectorPseudoClass(const std::string& test) + { + return Util::equalsLiteral("not", test) + || Util::equalsLiteral("matches", test) + || Util::equalsLiteral("current", test) + || Util::equalsLiteral("any", test) + || Util::equalsLiteral("has", test) + || Util::equalsLiteral("host", test) + || Util::equalsLiteral("host-context", test); + } + // EO isSelectorPseudoClass + + // ########################################################################### + // Pseudo-element selectors that take unadorned selectors as arguments. + // ########################################################################### + inline bool isSelectorPseudoElement(const std::string& test) + { + return Util::equalsLiteral("slotted", test); + } + // EO isSelectorPseudoElement + + // ########################################################################### + // Pseudo-element selectors that has binominals + // ########################################################################### + inline bool isSelectorPseudoBinominal(const std::string& test) + { + return Util::equalsLiteral("nth-child", test) + || Util::equalsLiteral("nth-last-child", test); + } + // isSelectorPseudoBinominal + + // ########################################################################### + // ########################################################################### + +} + +#endif diff --git a/src/libsass/src/ast_sel_cmp.cpp b/src/libsass/src/ast_sel_cmp.cpp new file mode 100644 index 000000000..61615d39d --- /dev/null +++ b/src/libsass/src/ast_sel_cmp.cpp @@ -0,0 +1,396 @@ +// sass.hpp must go before all system headers to get the +// __EXTENSIONS__ fix on Solaris. +#include "sass.hpp" + +#include "ast_selectors.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"); + } + + /*#########################################################################*/ + /*#########################################################################*/ + + bool SelectorList::operator== (const SelectorList& rhs) const + { + if (&rhs == this) return true; + if (rhs.length() != length()) return false; + std::unordered_set lhs_set; + lhs_set.reserve(length()); + 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; + } + 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(); + if (len != rlen) return false; + for (size_t i = 0; i < len; i += 1) { + if (*get(i) != *rhs.get(i)) return false; + } + return true; + } + + /*#########################################################################*/ + // Compare ComplexSelector against all other selector types + /*#########################################################################*/ + + bool 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; + std::unordered_set lhs_set; + lhs_set.reserve(length()); + for (const SimpleSelectorObj& element : elements()) { + lhs_set.insert(element.ptr()); + } + // there is no break?! + for (const SimpleSelectorObj& element : rhs.elements()) { + if (lhs_set.find(element.ptr()) == lhs_set.end()) return false; + } + return true; + } + + + /*#########################################################################*/ + // 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 Id_Selector::operator== (const SimpleSelector& rhs) const + { + auto sel = Cast(&rhs); + return sel ? *this == *sel : false; + } + + bool Type_Selector::operator== (const SimpleSelector& rhs) const + { + auto sel = Cast(&rhs); + return sel ? *this == *sel : false; + } + + bool Class_Selector::operator== (const SimpleSelector& rhs) const + { + auto sel = Cast(&rhs); + return sel ? *this == *sel : false; + } + + bool Pseudo_Selector::operator== (const SimpleSelector& rhs) const + { + auto sel = Cast(&rhs); + return sel ? *this == *sel : false; + } + + bool Attribute_Selector::operator== (const SimpleSelector& rhs) const + { + auto sel = Cast(&rhs); + return sel ? *this == *sel : false; + } + + bool Placeholder_Selector::operator== (const SimpleSelector& rhs) const + { + auto sel = Cast(&rhs); + return sel ? *this == *sel : false; + } + + /*#########################################################################*/ + /*#########################################################################*/ + + bool Id_Selector::operator== (const Id_Selector& rhs) const + { + // ID has no namespacing + return name() == rhs.name(); + } + + bool Type_Selector::operator== (const Type_Selector& rhs) const + { + return is_ns_eq(rhs) && name() == rhs.name(); + } + + bool Class_Selector::operator== (const Class_Selector& rhs) const + { + // Class has no namespacing + return name() == rhs.name(); + } + + bool Placeholder_Selector::operator== (const Placeholder_Selector& rhs) const + { + // Placeholder has no namespacing + return name() == rhs.name(); + } + + bool Attribute_Selector::operator== (const Attribute_Selector& 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; } + } + + bool Pseudo_Selector::operator== (const Pseudo_Selector& 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; } + } + + /*#########################################################################*/ + /*#########################################################################*/ + +} diff --git a/src/libsass/src/ast_sel_super.cpp b/src/libsass/src/ast_sel_super.cpp new file mode 100644 index 000000000..bc73f6ef7 --- /dev/null +++ b/src/libsass/src/ast_sel_super.cpp @@ -0,0 +1,539 @@ +// sass.hpp must go before all system headers to get the +// __EXTENSIONS__ fix on Solaris. +#include "sass.hpp" +#include "ast.hpp" + +#include "util_string.hpp" + +namespace Sass { + + // ########################################################################## + // To compare/debug against libsass you can use debugger.hpp: + // c++: std::cerr << "result " << debug_vec(compound) << "\n"; + // dart: stderr.writeln("result " + compound.toString()); + // ########################################################################## + + // ########################################################################## + // Returns whether [list1] is a superselector of [list2]. + // That is, whether [list1] matches every element that + // [list2] matches, as well as possibly additional elements. + // ########################################################################## + bool listIsSuperslector( + const std::vector& list1, + const std::vector& list2); + + // ########################################################################## + // Returns whether [complex1] is a superselector of [complex2]. + // That is, whether [complex1] matches every element that + // [complex2] matches, as well as possibly additional elements. + // ########################################################################## + bool complexIsSuperselector( + const std::vector& complex1, + const std::vector& complex2); + + // ########################################################################## + // Returns all pseudo selectors in [compound] that have + // a selector argument, and that have the given [name]. + // ########################################################################## + std::vector selectorPseudoNamed( + CompoundSelectorObj compound, std::string name) + { + std::vector rv; + for (SimpleSelectorObj sel : compound->elements()) { + if (Pseudo_Selector_Obj pseudo = Cast(sel)) { + if (pseudo->isClass() && pseudo->selector()) { + if (sel->name() == name) { + rv.push_back(sel); + } + } + } + } + return rv; + } + // EO selectorPseudoNamed + + // ########################################################################## + // Returns whether [simple1] is a superselector of [simple2]. + // That is, whether [simple1] matches every element that + // [simple2] matches, as well as possibly additional elements. + // ########################################################################## + bool simpleIsSuperselector( + const SimpleSelectorObj& simple1, + const SimpleSelectorObj& simple2) + { + // If they are equal they are superselectors + if (ObjEqualityFn(simple1, simple2)) { + return true; + } + // Some selector pseudoclasses can match normal selectors. + if (const Pseudo_Selector* pseudo = Cast(simple2)) { + if (pseudo->selector() && isSubselectorPseudo(pseudo->normalized())) { + for (auto complex : pseudo->selector()->elements()) { + // Make sure we have exacly one items + if (complex->length() != 1) { + return false; + } + // That items must be a compound selector + if (auto compound = Cast(complex->at(0))) { + // It must contain the lhs simple selector + if (!compound->contains(simple1)) { + return false; + } + } + } + return true; + } + } + return false; + } + // EO simpleIsSuperselector + + // ########################################################################## + // Returns whether [simple] is a superselector of [compound]. + // That is, whether [simple] matches every element that + // [compound] matches, as well as possibly additional elements. + // ########################################################################## + bool simpleIsSuperselectorOfCompound( + const SimpleSelectorObj& simple, + const CompoundSelectorObj& compound) + { + for (SimpleSelectorObj simple2 : compound->elements()) { + if (simpleIsSuperselector(simple, simple2)) { + return true; + } + } + return false; + } + // EO simpleIsSuperselectorOfCompound + + // ########################################################################## + // ########################################################################## + bool typeIsSuperselectorOfCompound( + const Type_Selector_Obj& type, + const CompoundSelectorObj& compound) + { + for (const SimpleSelectorObj& simple : compound->elements()) { + if (const Type_Selector_Obj& rhs = Cast(simple)) { + if (*type != *rhs) return true; + } + } + return false; + } + // EO typeIsSuperselectorOfCompound + + // ########################################################################## + // ########################################################################## + bool idIsSuperselectorOfCompound( + const Id_Selector_Obj& id, + const CompoundSelectorObj& compound) + { + for (const SimpleSelectorObj& simple : compound->elements()) { + if (const Id_Selector_Obj& rhs = Cast(simple)) { + if (*id != *rhs) return true; + } + } + return false; + } + // EO idIsSuperselectorOfCompound + + // ########################################################################## + // ########################################################################## + bool pseudoIsSuperselectorOfPseudo( + const Pseudo_Selector_Obj& pseudo1, + const Pseudo_Selector_Obj& pseudo2, + const ComplexSelectorObj& parent + ) + { + if (!pseudo2->selector()) return false; + if (pseudo1->name() == pseudo2->name()) { + SelectorListObj list = pseudo2->selector(); + return listIsSuperslector(list->elements(), { parent }); + } + return false; + } + // EO pseudoIsSuperselectorOfPseudo + + // ########################################################################## + // ########################################################################## + bool pseudoNotIsSuperselectorOfCompound( + const Pseudo_Selector_Obj& pseudo1, + const CompoundSelectorObj& compound2, + const ComplexSelectorObj& parent) + { + for (const SimpleSelectorObj& simple2 : compound2->elements()) { + if (const Type_Selector_Obj& type2 = Cast(simple2)) { + if (const CompoundSelectorObj& compound1 = Cast(parent->last())) { + if (typeIsSuperselectorOfCompound(type2, compound1)) return true; + } + } + else if (const Id_Selector_Obj& id2 = Cast(simple2)) { + if (const CompoundSelectorObj& compound1 = Cast(parent->last())) { + if (idIsSuperselectorOfCompound(id2, compound1)) return true; + } + } + else if (const Pseudo_Selector_Obj& pseudo2 = Cast(simple2)) { + if (pseudoIsSuperselectorOfPseudo(pseudo1, pseudo2, parent)) return true; + } + } + return false; + } + // pseudoNotIsSuperselectorOfCompound + + // ########################################################################## + // Returns whether [pseudo1] is a superselector of [compound2]. + // That is, whether [pseudo1] matches every element that [compound2] + // matches, as well as possibly additional elements. This assumes that + // [pseudo1]'s `selector` argument is not `null`. If [parents] is passed, + // it represents the parents of [compound2]. This is relevant for pseudo + // selectors with selector arguments, where we may need to know if the + // parent selectors in the selector argument match [parents]. + // ########################################################################## + bool selectorPseudoIsSuperselector( + const Pseudo_Selector_Obj& pseudo1, + const CompoundSelectorObj& compound2, + // ToDo: is this really the most convenient way to do this? + std::vector::const_iterator parents_from, + std::vector::const_iterator parents_to) + { + + // ToDo: move normalization function + std::string name(Util::unvendor(pseudo1->name())); + + if (name == "matches" || name == "any") { + std::vector pseudos = + selectorPseudoNamed(compound2, pseudo1->name()); + SelectorListObj selector1 = pseudo1->selector(); + for (Pseudo_Selector_Obj pseudo2 : pseudos) { + SelectorListObj selector = pseudo2->selector(); + if (selector1->isSuperselectorOf(selector)) { + return true; + } + } + + for (ComplexSelectorObj complex1 : selector1->elements()) { + std::vector parents; + for (auto cur = parents_from; cur != parents_to; cur++) { + parents.push_back(*cur); + } + parents.push_back(compound2); + if (complexIsSuperselector(complex1->elements(), parents)) { + return true; + } + } + + } + else if (name == "has" || name == "host" || name == "host-context" || name == "slotted") { + std::vector pseudos = + selectorPseudoNamed(compound2, pseudo1->name()); + SelectorListObj selector1 = pseudo1->selector(); + for (Pseudo_Selector_Obj pseudo2 : pseudos) { + SelectorListObj selector = pseudo2->selector(); + if (selector1->isSuperselectorOf(selector)) { + return true; + } + } + + } + else if (name == "not") { + for (ComplexSelectorObj complex : pseudo1->selector()->elements()) { + if (!pseudoNotIsSuperselectorOfCompound(pseudo1, compound2, complex)) return false; + } + return true; + } + else if (name == "current") { + std::vector pseudos = + selectorPseudoNamed(compound2, "current"); + for (Pseudo_Selector_Obj pseudo2 : pseudos) { + if (ObjEqualityFn(pseudo1, pseudo2)) return true; + } + + } + else if (name == "nth-child" || name == "nth-last-child") { + for (auto simple2 : compound2->elements()) { + if (Pseudo_Selector_Obj pseudo2 = simple2->getPseudoSelector()) { + if (pseudo1->name() != pseudo2->name()) continue; + if (!ObjEqualityFn(pseudo1->argument(), pseudo2->argument())) continue; + if (pseudo1->selector()->isSuperselectorOf(pseudo2->selector())) return true; + } + } + return false; + } + + return false; + + } + // EO selectorPseudoIsSuperselector + + // ########################################################################## + // Returns whether [compound1] is a superselector of [compound2]. + // That is, whether [compound1] matches every element that [compound2] + // matches, as well as possibly additional elements. If [parents] is + // passed, it represents the parents of [compound2]. This is relevant + // for pseudo selectors with selector arguments, where we may need to + // know if the parent selectors in the selector argument match [parents]. + // ########################################################################## + bool compoundIsSuperselector( + const CompoundSelectorObj& compound1, + const CompoundSelectorObj& compound2, + // ToDo: is this really the most convenient way to do this? + const std::vector::const_iterator parents_from, + const std::vector::const_iterator parents_to) + { + // Every selector in [compound1.components] must have + // a matching selector in [compound2.components]. + for (SimpleSelectorObj simple1 : compound1->elements()) { + Pseudo_Selector_Obj pseudo1 = Cast(simple1); + if (pseudo1 && pseudo1->selector()) { + if (!selectorPseudoIsSuperselector(pseudo1, compound2, parents_from, parents_to)) { + return false; + } + } + else if (!simpleIsSuperselectorOfCompound(simple1, compound2)) { + return false; + } + } + // [compound1] can't be a superselector of a selector + // with pseudo-elements that [compound2] doesn't share. + for (SimpleSelectorObj simple2 : compound2->elements()) { + Pseudo_Selector_Obj pseudo2 = Cast(simple2); + if (pseudo2 && pseudo2->isElement()) { + if (!simpleIsSuperselectorOfCompound(pseudo2, compound1)) { + return false; + } + } + } + return true; + } + // EO compoundIsSuperselector + + // ########################################################################## + // Returns whether [compound1] is a superselector of [compound2]. + // That is, whether [compound1] matches every element that [compound2] + // matches, as well as possibly additional elements. If [parents] is + // passed, it represents the parents of [compound2]. This is relevant + // for pseudo selectors with selector arguments, where we may need to + // know if the parent selectors in the selector argument match [parents]. + // ########################################################################## + bool compoundIsSuperselector( + const CompoundSelectorObj& compound1, + const CompoundSelectorObj& compound2, + const std::vector& parents) + { + return compoundIsSuperselector( + compound1, compound2, + parents.begin(), parents.end() + ); + } + // EO compoundIsSuperselector + + // ########################################################################## + // Returns whether [complex1] is a superselector of [complex2]. + // That is, whether [complex1] matches every element that + // [complex2] matches, as well as possibly additional elements. + // ########################################################################## + bool complexIsSuperselector( + const std::vector& complex1, + const std::vector& complex2) + { + + // Selectors with trailing operators are neither superselectors nor subselectors. + if (!complex1.empty() && Cast(complex1.back())) return false; + if (!complex2.empty() && Cast(complex2.back())) return false; + + size_t i1 = 0, i2 = 0; + while (true) { + + size_t remaining1 = complex1.size() - i1; + size_t remaining2 = complex2.size() - i2; + + if (remaining1 == 0 || remaining2 == 0) { + return false; + } + // More complex selectors are never + // superselectors of less complex ones. + if (remaining1 > remaining2) { + return false; + } + + // Selectors with leading operators are + // neither superselectors nor subselectors. + if (Cast(complex1[i1])) { + return false; + } + if (Cast(complex2[i2])) { + return false; + } + + CompoundSelectorObj compound1 = Cast(complex1[i1]); + CompoundSelectorObj compound2 = Cast(complex2.back()); + + if (remaining1 == 1) { + std::vector::const_iterator parents_to = complex2.end(); + std::vector::const_iterator parents_from = complex2.begin(); + std::advance(parents_from, i2 + 1); // equivalent to dart `.skip(i2 + 1)` + bool rv = compoundIsSuperselector(compound1, compound2, parents_from, parents_to); + std::vector pp; + + std::vector::const_iterator end = parents_to; + std::vector::const_iterator beg = parents_from; + while (beg != end) { + pp.push_back(*beg); + beg++; + } + + return rv; + } + + // Find the first index where `complex2.sublist(i2, afterSuperselector)` + // is a subselector of [compound1]. We stop before the superselector + // would encompass all of [complex2] because we know [complex1] has + // more than one element, and consuming all of [complex2] wouldn't + // leave anything for the rest of [complex1] to match. + size_t afterSuperselector = i2 + 1; + for (; afterSuperselector < complex2.size(); afterSuperselector++) { + SelectorComponentObj component2 = complex2[afterSuperselector - 1]; + if (CompoundSelectorObj compound2 = Cast(component2)) { + std::vector::const_iterator parents_to = complex2.begin(); + std::vector::const_iterator parents_from = complex2.begin(); + // complex2.take(afterSuperselector - 1).skip(i2 + 1) + std::advance(parents_from, i2 + 1); // equivalent to dart `.skip` + std::advance(parents_to, afterSuperselector); // equivalent to dart `.take` + if (compoundIsSuperselector(compound1, compound2, parents_from, parents_to)) { + break; + } + } + } + if (afterSuperselector == complex2.size()) { + return false; + } + + SelectorComponentObj component1 = complex1[i1 + 1], + component2 = complex2[afterSuperselector]; + + SelectorCombinatorObj combinator1 = Cast(component1); + SelectorCombinatorObj combinator2 = Cast(component2); + + if (!combinator1.isNull()) { + + if (combinator2.isNull()) { + return false; + } + // `.a ~ .b` is a superselector of `.a + .b`, + // but otherwise the combinators must match. + if (combinator1->isGeneralCombinator()) { + if (combinator2->isChildCombinator()) { + return false; + } + } + else if (*combinator1 != *combinator2) { + return false; + } + + // `.foo > .baz` is not a superselector of `.foo > .bar > .baz` or + // `.foo > .bar .baz`, despite the fact that `.baz` is a superselector of + // `.bar > .baz` and `.bar .baz`. Same goes for `+` and `~`. + if (remaining1 == 3 && remaining2 > 3) { + return false; + } + + i1 += 2; i2 = afterSuperselector + 1; + + } + else if (!combinator2.isNull()) { + if (!combinator2->isChildCombinator()) { + return false; + } + i1 += 1; i2 = afterSuperselector + 1; + } + else { + i1 += 1; i2 = afterSuperselector; + } + } + + return false; + + } + // EO complexIsSuperselector + + // ########################################################################## + // Like [complexIsSuperselector], but compares [complex1] + // and [complex2] as though they shared an implicit base + // [SimpleSelector]. For example, `B` is not normally a + // superselector of `B A`, since it doesn't match elements + // that match `A`. However, it *is* a parent superselector, + // since `B X` is a superselector of `B A X`. + // ########################################################################## + bool complexIsParentSuperselector( + const std::vector& complex1, + const std::vector& complex2) + { + // Try some simple heuristics to see if we can avoid allocations. + if (complex1.empty() && complex2.empty()) return false; + if (Cast(complex1.front())) return false; + if (Cast(complex2.front())) return false; + if (complex1.size() > complex2.size()) return false; + // TODO(nweiz): There's got to be a way to do this without a bunch of extra allocations... + std::vector cplx1(complex1); + std::vector cplx2(complex2); + CompoundSelectorObj base = SASS_MEMORY_NEW(CompoundSelector, "[tmp]"); + cplx1.push_back(base); cplx2.push_back(base); + return complexIsSuperselector(cplx1, cplx2); + } + // EO complexIsParentSuperselector + + // ########################################################################## + // Returns whether [list] has a superselector for [complex]. + // That is, whether an item in [list] matches every element that + // [complex] matches, as well as possibly additional elements. + // ########################################################################## + bool listHasSuperslectorForComplex( + std::vector list, + ComplexSelectorObj complex) + { + // Return true if every [complex] selector on [list2] + // is a super selector of the full selector [list1]. + for (ComplexSelectorObj lhs : list) { + if (complexIsSuperselector(lhs->elements(), complex->elements())) { + return true; + } + } + return false; + } + // listIsSuperslectorOfComplex + + // ########################################################################## + // Returns whether [list1] is a superselector of [list2]. + // That is, whether [list1] matches every element that + // [list2] matches, as well as possibly additional elements. + // ########################################################################## + bool listIsSuperslector( + const std::vector& list1, + const std::vector& list2) + { + // Return true if every [complex] selector on [list2] + // is a super selector of the full selector [list1]. + for (ComplexSelectorObj complex : list2) { + if (!listHasSuperslectorForComplex(list1, complex)) { + return false; + } + } + return true; + } + // EO listIsSuperslector + + // ########################################################################## + // Implement selector methods (dispatch to functions) + // ########################################################################## + bool SelectorList::isSuperselectorOf(const SelectorList* sub) const + { + return listIsSuperslector(elements(), sub->elements()); + } + bool ComplexSelector::isSuperselectorOf(const ComplexSelector* sub) const + { + return complexIsSuperselector(elements(), sub->elements()); + } + + // ########################################################################## + // ########################################################################## + +} diff --git a/src/libsass/src/ast_sel_unify.cpp b/src/libsass/src/ast_sel_unify.cpp new file mode 100644 index 000000000..cf05dc615 --- /dev/null +++ b/src/libsass/src/ast_sel_unify.cpp @@ -0,0 +1,275 @@ +// sass.hpp must go before all system headers to get the +// __EXTENSIONS__ fix on Solaris. +#include "sass.hpp" + +#include "ast.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 + // ########################################################################## + std::vector> unifyComplex( + const std::vector>& complexes) + { + + SASS_ASSERT(!complexes.empty(), "Can't unify empty list"); + if (complexes.size() == 1) return complexes; + + CompoundSelectorObj unifiedBase = SASS_MEMORY_NEW(CompoundSelector, ParserState("[phony]")); + for (auto complex : complexes) { + SelectorComponentObj base = complex.back(); + if (CompoundSelector * comp = base->getCompound()) { + if (unifiedBase->empty()) { + unifiedBase->concat(comp); + } + else { + for (SimpleSelectorObj simple : comp->elements()) { + unifiedBase = simple->unifyWith(unifiedBase); + if (unifiedBase.isNull()) return {}; + } + } + } + else { + return {}; + } + } + + std::vector> complexesWithoutBases; + for (size_t i = 0; i < complexes.size(); i += 1) { + std::vector sel = complexes[i]; + sel.pop_back(); // remove last item (base) from the list + complexesWithoutBases.push_back(std::move(sel)); + } + + complexesWithoutBases.back().push_back(unifiedBase); + + return weave(complexesWithoutBases); + + } + // EO unifyComplex + + // ########################################################################## + // Returns a [CompoundSelector] that matches only elements + // that are matched by both [compound1] and [compound2]. + // If no such selector can be produced, returns `null`. + // ########################################################################## + CompoundSelector* CompoundSelector::unifyWith(CompoundSelector* rhs) + { + if (empty()) return rhs; + CompoundSelectorObj unified = SASS_MEMORY_COPY(rhs); + for (const SimpleSelectorObj& sel : elements()) { + unified = sel->unifyWith(unified); + if (unified.isNull()) break; + } + return unified.detach(); + } + // EO CompoundSelector::unifyWith(CompoundSelector*) + + // ########################################################################## + // Returns the compoments of a [CompoundSelector] that matches only elements + // matched by both this and [compound]. By default, this just returns a copy + // of [compound] with this selector added to the end, or returns the original + // array if this selector already exists in it. Returns `null` if unification + // is impossible—for example, if there are multiple ID selectors. + // ########################################################################## + // This is implemented in `selector/simple.dart` as `SimpleSelector::unify` + // ########################################################################## + CompoundSelector* SimpleSelector::unifyWith(CompoundSelector* rhs) + { + + 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; + } + } + for (const SimpleSelectorObj& sel : rhs->elements()) { + if (*this == *sel) { + return rhs; + } + } + + CompoundSelectorObj result = SASS_MEMORY_NEW(CompoundSelector, rhs->pstate()); + + bool addedThis = false; + for (auto simple : rhs->elements()) { + // Make sure pseudo selectors always come last. + if (!addedThis && simple->getPseudoSelector()) { + result->append(this); + addedThis = true; + } + result->append(simple); + } + + if (!addedThis) { + result->append(this); + } + return result.detach(); + + } + // EO SimpleSelector::unifyWith(CompoundSelector*) + + // ########################################################################## + // This is implemented in `selector/type.dart` as `PseudoSelector::unify` + // ########################################################################## + CompoundSelector* Type_Selector::unifyWith(CompoundSelector* rhs) + { + if (rhs->empty()) { + rhs->append(this); + return rhs; + } + Type_Selector* type = Cast(rhs->at(0)); + if (type != nullptr) { + SimpleSelector* unified = unifyWith(type); + if (unified == nullptr) { + return nullptr; + } + rhs->elements()[0] = unified; + } + else if (!is_universal() || (has_ns_ && ns_ != "*")) { + rhs->insert(rhs->begin(), this); + } + return rhs; + } + + // ########################################################################## + // This is implemented in `selector/id.dart` as `PseudoSelector::unify` + // ########################################################################## + CompoundSelector* Id_Selector::unifyWith(CompoundSelector* rhs) + { + for (const SimpleSelector* sel : rhs->elements()) { + if (const Id_Selector* id_sel = Cast(sel)) { + if (id_sel->name() != name()) return nullptr; + } + } + return SimpleSelector::unifyWith(rhs); + } + + // ########################################################################## + // This is implemented in `selector/pseudo.dart` as `PseudoSelector::unify` + // ########################################################################## + CompoundSelector* Pseudo_Selector::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()); + + bool addedThis = false; + for (auto simple : compound->elements()) { + // Make sure pseudo selectors always come last. + if (Pseudo_Selector_Obj pseudo = simple->getPseudoSelector()) { + if (pseudo->isElement()) { + // A given compound selector may only contain one pseudo element. If + // [compound] has a different one than [this], unification fails. + if (isElement()) { + return {}; + } + // Otherwise, this is a pseudo selector and + // should come before pseduo elements. + result->append(this); + addedThis = true; + } + } + result->append(simple); + } + + if (!addedThis) { + result->append(this); + } + + return result.detach(); + + } + // EO Pseudo_Selector::unifyWith(CompoundSelector* + + // ########################################################################## + // This is implemented in `extend/functions.dart` as `unifyUniversalAndElement` + // Returns a [SimpleSelector] that matches only elements that are matched by + // both [selector1] and [selector2], which must both be either [UniversalSelector]s + // or [TypeSelector]s. If no such selector can be produced, returns `null`. + // Note: libsass handles universal selector directly within the type selector + // ########################################################################## + SimpleSelector* Type_Selector::unifyWith(const SimpleSelector* rhs) + { + bool rhs_ns = false; + if (!(is_ns_eq(*rhs) || rhs->is_universal_ns())) { + if (!is_universal_ns()) { + return nullptr; + } + rhs_ns = true; + } + bool rhs_name = false; + if (!(name_ == rhs->name() || rhs->is_universal())) { + if (!(is_universal())) { + return nullptr; + } + rhs_name = true; + } + if (rhs_ns) { + ns(rhs->ns()); + has_ns(rhs->has_ns()); + } + if (rhs_name) name(rhs->name()); + return this; + } + // EO Type_Selector::unifyWith(const SimpleSelector*) + + // ########################################################################## + // Unify two complex selectors. Internally calls `unifyComplex` + // and then wraps the result in newly create ComplexSelectors. + // ########################################################################## + SelectorList* ComplexSelector::unifyWith(ComplexSelector* rhs) + { + SelectorListObj list = SASS_MEMORY_NEW(SelectorList, pstate()); + std::vector> rv = + unifyComplex({ elements(), rhs->elements() }); + for (std::vector items : rv) { + ComplexSelectorObj sel = SASS_MEMORY_NEW(ComplexSelector, pstate()); + sel->elements() = std::move(items); + list->append(sel); + } + return list.detach(); + } + // EO ComplexSelector::unifyWith(ComplexSelector*) + + // ########################################################################## + // only called from the sass function `selector-unify` + // ########################################################################## + SelectorList* SelectorList::unifyWith(SelectorList* rhs) + { + SelectorList* slist = SASS_MEMORY_NEW(SelectorList, pstate()); + // Unify all of children with RHS's children, + // storing the results in `unified_complex_selectors` + for (ComplexSelectorObj& seq1 : elements()) { + for (ComplexSelectorObj& seq2 : rhs->elements()) { + if (SelectorListObj unified = seq1->unifyWith(seq2)) { + std::move(unified->begin(), unified->end(), + std::inserter(slist->elements(), slist->end())); + } + } + } + return slist; + } + // EO SelectorList::unifyWith(SelectorList*) + + // ########################################################################## + // ########################################################################## + +} diff --git a/src/libsass/src/ast_sel_weave.cpp b/src/libsass/src/ast_sel_weave.cpp new file mode 100644 index 000000000..c792d0f8a --- /dev/null +++ b/src/libsass/src/ast_sel_weave.cpp @@ -0,0 +1,616 @@ +// sass.hpp must go before all system headers to get the +// __EXTENSIONS__ fix on Solaris. +#include "sass.hpp" + +#include "ast.hpp" +#include "permutate.hpp" +#include "dart_helpers.hpp" + +namespace Sass { + + // ########################################################################## + // Returns whether or not [compound] contains a `::root` selector. + // ########################################################################## + bool hasRoot(const CompoundSelector* compound) + { + // Libsass does not yet know the root selector + return false; + } + // EO hasRoot + + // ########################################################################## + // Returns whether a [CompoundSelector] may contain only + // one simple selector of the same type as [simple]. + // ########################################################################## + bool isUnique(const SimpleSelector* simple) + { + if (Cast(simple)) return true; + if (const Pseudo_Selector * pseudo = Cast(simple)) { + if (pseudo->is_pseudo_element()) return true; + } + return false; + } + // EO isUnique + + // ########################################################################## + // Returns whether [complex1] and [complex2] need to be unified to + // produce a valid combined selector. This is necessary when both + // selectors contain the same unique simple selector, such as an ID. + // ########################################################################## + bool mustUnify( + const std::vector& complex1, + const std::vector& complex2) + { + + std::vector uniqueSelectors1; + for (const SelectorComponent* component : complex1) { + if (const CompoundSelector * compound = component->getCompound()) { + for (const SimpleSelector* sel : compound->elements()) { + if (isUnique(sel)) { + uniqueSelectors1.push_back(sel); + } + } + } + } + if (uniqueSelectors1.empty()) return false; + + // ToDo: unsure if this is correct + for (const SelectorComponent* component : complex2) { + if (const CompoundSelector * compound = component->getCompound()) { + for (const SimpleSelector* sel : compound->elements()) { + if (isUnique(sel)) { + for (auto check : uniqueSelectors1) { + if (*check == *sel) return true; + } + } + } + } + } + + return false; + + } + // EO isUnique + + // ########################################################################## + // Helper function used by `weaveParents` + // ########################################################################## + bool cmpGroups( + const std::vector& group1, + const std::vector& group2, + std::vector& select) + { + + if (group1.size() == group2.size() && std::equal(group1.begin(), group1.end(), group2.begin(), PtrObjEqualityFn)) { + select = group1; + return true; + } + + if (!Cast(group1.front())) { + select = {}; + return false; + } + if (!Cast(group2.front())) { + select = {}; + return false; + } + + if (complexIsParentSuperselector(group1, group2)) { + select = group2; + return true; + } + if (complexIsParentSuperselector(group2, group1)) { + select = group1; + return true; + } + + if (!mustUnify(group1, group2)) { + select = {}; + return false; + } + + std::vector> unified + = unifyComplex({ group1, group2 }); + if (unified.empty()) return false; + if (unified.size() > 1) return false; + select = unified.front(); + return true; + } + // EO cmpGroups + + // ########################################################################## + // Helper function used by `weaveParents` + // ########################################################################## + template + bool checkForEmptyChild(const T& item) { + return item.empty(); + } + // EO checkForEmptyChild + + // ########################################################################## + // Helper function used by `weaveParents` + // ########################################################################## + bool cmpChunkForEmptySequence( + const std::vector>& seq, + const std::vector& group) + { + return seq.empty(); + } + // EO cmpChunkForEmptySequence + + // ########################################################################## + // Helper function used by `weaveParents` + // ########################################################################## + bool cmpChunkForParentSuperselector( + const std::vector< std::vector>& seq, + const std::vector& group) + { + return seq.empty() || complexIsParentSuperselector(seq.front(), group); + } + // EO cmpChunkForParentSuperselector + + // ########################################################################## + // Returns all orderings of initial subseqeuences of [queue1] and [queue2]. + // The [done] callback is used to determine the extent of the initial + // subsequences. It's called with each queue until it returns `true`. + // Destructively removes the initial subsequences of [queue1] and [queue2]. + // For example, given `(A B C | D E)` and `(1 2 | 3 4 5)` (with `|` denoting + // the boundary of the initial subsequence), this would return `[(A B C 1 2), + // (1 2 A B C)]`. The queues would then contain `(D E)` and `(3 4 5)`. + // ########################################################################## + template + std::vector> getChunks( + std::vector& queue1, std::vector& queue2, + const T& group, bool(*done)(const std::vector&, const T&) + ) { + + std::vector chunk1; + while (!done(queue1, group)) { + chunk1.push_back(queue1.front()); + queue1.erase(queue1.begin()); + } + + std::vector chunk2; + while (!done(queue2, group)) { + chunk2.push_back(queue2.front()); + queue2.erase(queue2.begin()); + } + + if (chunk1.empty() && chunk2.empty()) return {}; + else if (chunk1.empty()) return { chunk2 }; + else if (chunk2.empty()) return { chunk1 }; + + std::vector choice1(chunk1), choice2(chunk2); + std::move(std::begin(chunk2), std::end(chunk2), + std::inserter(choice1, std::end(choice1))); + std::move(std::begin(chunk1), std::end(chunk1), + std::inserter(choice2, std::end(choice2))); + return { choice1, choice2 }; + } + // EO getChunks + + // ########################################################################## + // If the first element of [queue] has a `::root` + // selector, removes and returns that element. + // ########################################################################## + CompoundSelectorObj getFirstIfRoot(std::vector& queue) { + if (queue.empty()) return {}; + SelectorComponent* first = queue.front(); + if (CompoundSelector* sel = Cast(first)) { + if (!hasRoot(sel)) return {}; + queue.erase(queue.begin()); + return sel; + } + return {}; + } + // EO getFirstIfRoot + + // ########################################################################## + // Returns [complex], grouped into sub-lists such that no sub-list + // contains two adjacent [ComplexSelector]s. For example, + // `(A B > C D + E ~ > G)` is grouped into `[(A) (B > C) (D + E ~ > G)]`. + // ########################################################################## + std::vector> groupSelectors( + const std::vector& components) + { + bool lastWasCompound = false; + std::vector group; + std::vector> groups; + for (size_t i = 0; i < components.size(); i += 1) { + if (CompoundSelector* compound = components[i]->getCompound()) { + if (lastWasCompound) { + groups.push_back(group); + group.clear(); + } + group.push_back(compound); + lastWasCompound = true; + } + else if (SelectorCombinator* combinator = components[i]->getCombinator()) { + group.push_back(combinator); + lastWasCompound = false; + } + } + if (!group.empty()) { + groups.push_back(group); + } + return groups; + } + // EO groupSelectors + + // ########################################################################## + // Extracts leading [Combinator]s from [components1] and [components2] + // and merges them together into a single list of combinators. + // If there are no combinators to be merged, returns an empty list. + // If the combinators can't be merged, returns `null`. + // ########################################################################## + bool mergeInitialCombinators( + std::vector& components1, + std::vector& components2, + std::vector& result) + { + + std::vector combinators1; + while (!components1.empty() && Cast(components1.front())) { + SelectorCombinatorObj front = Cast(components1.front()); + components1.erase(components1.begin()); + combinators1.push_back(front); + } + + std::vector combinators2; + while (!components2.empty() && Cast(components2.front())) { + SelectorCombinatorObj front = Cast(components2.front()); + components2.erase(components2.begin()); + combinators2.push_back(front); + } + + // If neither sequence of combinators is a subsequence + // of the other, they cannot be merged successfully. + std::vector LCS = lcs(combinators1, combinators2); + + if (ListEquality(LCS, combinators1, PtrObjEqualityFn)) { + result = combinators2; + return true; + } + if (ListEquality(LCS, combinators2, PtrObjEqualityFn)) { + result = combinators1; + return true; + } + + return false; + + } + // EO mergeInitialCombinators + + // ########################################################################## + // Extracts trailing [Combinator]s, and the selectors to which they apply, + // from [components1] and [components2] and merges them together into a + // single list. If there are no combinators to be merged, returns an + // empty list. If the sequences can't be merged, returns `null`. + // ########################################################################## + bool mergeFinalCombinators( + std::vector& components1, + std::vector& components2, + std::vector>>& result) + { + + if (components1.empty() || !Cast(components1.back())) { + if (components2.empty() || !Cast(components2.back())) { + return true; + } + } + + std::vector combinators1; + while (!components1.empty() && Cast(components1.back())) { + SelectorCombinatorObj back = Cast(components1.back()); + components1.erase(components1.end() - 1); + combinators1.push_back(back); + } + + std::vector combinators2; + while (!components2.empty() && Cast(components2.back())) { + SelectorCombinatorObj back = Cast(components2.back()); + components2.erase(components2.end() - 1); + combinators2.push_back(back); + } + + // reverse now as we used push_back (faster than new alloc) + std::reverse(combinators1.begin(), combinators1.end()); + std::reverse(combinators2.begin(), combinators2.end()); + + if (combinators1.size() > 1 || combinators2.size() > 1) { + // If there are multiple combinators, something hacky's going on. If one + // is a supersequence of the other, use that, otherwise give up. + auto LCS = lcs(combinators1, combinators2); + if (ListEquality(LCS, combinators1, PtrObjEqualityFn)) { + result.push_back({ combinators2 }); + } + else if (ListEquality(LCS, combinators2, PtrObjEqualityFn)) { + result.push_back({ combinators1 }); + } + else { + return false; + } + return true; + } + + // This code looks complicated, but it's actually just a bunch of special + // cases for interactions between different combinators. + SelectorCombinatorObj combinator1, combinator2; + if (!combinators1.empty()) combinator1 = combinators1.back(); + if (!combinators2.empty()) combinator2 = combinators2.back(); + + if (!combinator1.isNull() && !combinator2.isNull()) { + + CompoundSelector* compound1 = Cast(components1.back()); + CompoundSelector* compound2 = Cast(components2.back()); + + components1.pop_back(); + components2.pop_back(); + + if (combinator1->isGeneralCombinator() && combinator2->isGeneralCombinator()) { + + if (compound1->isSuperselectorOf(compound2)) { + result.push_back({ { compound2, combinator2 } }); + } + else if (compound2->isSuperselectorOf(compound1)) { + result.push_back({ { compound1, combinator1 } }); + } + else { + std::vector> choices; + choices.push_back({ compound1, combinator1, compound2, combinator2 }); + choices.push_back({ compound2, combinator2, compound1, combinator1 }); + if (CompoundSelector* unified = compound1->unifyWith(compound2)) { + choices.push_back({ unified, combinator1 }); + } + result.push_back(choices); + } + } + else if ((combinator1->isGeneralCombinator() && combinator2->isAdjacentCombinator()) || + (combinator1->isAdjacentCombinator() && combinator2->isGeneralCombinator())) { + + CompoundSelector* followingSiblingSelector = combinator1->isGeneralCombinator() ? compound1 : compound2; + CompoundSelector* nextSiblingSelector = combinator1->isGeneralCombinator() ? compound2 : compound1; + SelectorCombinator* followingSiblingCombinator = combinator1->isGeneralCombinator() ? combinator1 : combinator2; + SelectorCombinator* nextSiblingCombinator = combinator1->isGeneralCombinator() ? combinator2 : combinator1; + + if (followingSiblingSelector->isSuperselectorOf(nextSiblingSelector)) { + result.push_back({ { nextSiblingSelector, nextSiblingCombinator } }); + } + else { + CompoundSelectorObj unified = compound1->unifyWith(compound2); + std::vector> items; + + if (!unified.isNull()) { + items.push_back({ + unified, nextSiblingCombinator + }); + } + + items.insert(items.begin(), { + followingSiblingSelector, + followingSiblingCombinator, + nextSiblingSelector, + nextSiblingCombinator, + }); + + result.push_back(items); + } + + } + else if (combinator1->isChildCombinator() && (combinator2->isAdjacentCombinator() || combinator2->isGeneralCombinator())) { + result.push_back({ { compound2, combinator2 } }); + components1.push_back(compound1); + components1.push_back(combinator1); + } + else if (combinator2->isChildCombinator() && (combinator1->isAdjacentCombinator() || combinator1->isGeneralCombinator())) { + result.push_back({ { compound1, combinator1 } }); + components2.push_back(compound2); + components2.push_back(combinator2); + } + else if (*combinator1 == *combinator2) { + CompoundSelectorObj unified = compound1->unifyWith(compound2); + if (unified.isNull()) return false; + result.push_back({ { unified, combinator1 } }); + } + else { + return false; + } + + return mergeFinalCombinators(components1, components2, result); + + } + else if (!combinator1.isNull()) { + + if (combinator1->isChildCombinator() && !components2.empty()) { + const CompoundSelector* back1 = Cast(components1.back()); + const CompoundSelector* back2 = Cast(components2.back()); + if (back1 && back2 && back2->isSuperselectorOf(back1)) { + components2.pop_back(); + } + } + + result.push_back({ { components1.back(), combinator1 } }); + + components1.pop_back(); + + return mergeFinalCombinators(components1, components2, result); + + } + + if (combinator2->isChildCombinator() && !components1.empty()) { + const CompoundSelector* back1 = Cast(components1.back()); + const CompoundSelector* back2 = Cast(components2.back()); + if (back1 && back2 && back1->isSuperselectorOf(back2)) { + components1.pop_back(); + } + } + + result.push_back({ { components2.back(), combinator2 } }); + + components2.pop_back(); + + return mergeFinalCombinators(components1, components2, result); + + } + // EO mergeFinalCombinators + + // ########################################################################## + // Expands "parenthesized selectors" in [complexes]. That is, if + // we have `.A .B {@extend .C}` and `.D .C {...}`, this conceptually + // expands into `.D .C, .D (.A .B)`, and this function translates + // `.D (.A .B)` into `.D .A .B, .A .D .B`. For thoroughness, `.A.D .B` + // would also be required, but including merged selectors results in + // exponential output for very little gain. The selector `.D (.A .B)` + // is represented as the list `[[.D], [.A, .B]]`. + // ########################################################################## + std::vector> weave( + const std::vector>& complexes) { + + std::vector> prefixes; + + prefixes.push_back(complexes.at(0)); + + for (size_t i = 1; i < complexes.size(); i += 1) { + + if (complexes[i].empty()) { + continue; + } + const std::vector& complex = complexes[i]; + SelectorComponent* target = complex.back(); + if (complex.size() == 1) { + for (auto& prefix : prefixes) { + prefix.push_back(target); + } + continue; + } + + std::vector parents(complex); + + parents.pop_back(); + + std::vector> newPrefixes; + for (std::vector prefix : prefixes) { + std::vector> + parentPrefixes = weaveParents(prefix, parents); + if (parentPrefixes.empty()) continue; + for (auto& parentPrefix : parentPrefixes) { + parentPrefix.push_back(target); + newPrefixes.push_back(parentPrefix); + } + } + prefixes = newPrefixes; + + } + return prefixes; + + } + // EO weave + + // ########################################################################## + // Interweaves [parents1] and [parents2] as parents of the same target + // selector. Returns all possible orderings of the selectors in the + // inputs (including using unification) that maintain the relative + // ordering of the input. For example, given `.foo .bar` and `.baz .bang`, + // this would return `.foo .bar .baz .bang`, `.foo .bar.baz .bang`, + // `.foo .baz .bar .bang`, `.foo .baz .bar.bang`, `.foo .baz .bang .bar`, + // and so on until `.baz .bang .foo .bar`. Semantically, for selectors A + // and B, this returns all selectors `AB_i` such that the union over all i + // of elements matched by `AB_i X` is identical to the intersection of all + // elements matched by `A X` and all elements matched by `B X`. Some `AB_i` + // are elided to reduce the size of the output. + // ########################################################################## + std::vector> weaveParents( + std::vector queue1, + std::vector queue2) + { + + std::vector leads; + std::vector>> trails; + if (!mergeInitialCombinators(queue1, queue2, leads)) return {}; + if (!mergeFinalCombinators(queue1, queue2, trails)) return {}; + // list comes out in reverse order for performance + std::reverse(trails.begin(), trails.end()); + + // Make sure there's at most one `:root` in the output. + // Note: does not yet do anything in libsass (no root selector) + CompoundSelectorObj root1 = getFirstIfRoot(queue1); + CompoundSelectorObj root2 = getFirstIfRoot(queue2); + + if (!root1.isNull() && !root2.isNull()) { + CompoundSelectorObj root = root1->unifyWith(root2); + if (root.isNull()) return {}; // null + queue1.insert(queue1.begin(), root); + queue2.insert(queue2.begin(), root); + } + else if (!root1.isNull()) { + queue2.insert(queue2.begin(), root1); + } + else if (!root2.isNull()) { + queue1.insert(queue1.begin(), root2); + } + + // group into sub-lists so no sub-list contains two adjacent ComplexSelectors. + std::vector> groups1 = groupSelectors(queue1); + std::vector> groups2 = groupSelectors(queue2); + + // The main array to store our choices that will be permutated + std::vector>> choices; + + // append initial combinators + choices.push_back({ leads }); + + std::vector> LCS = + lcs>(groups1, groups2, cmpGroups); + + for (auto group : LCS) { + + // Create junks from groups1 and groups2 + std::vector>> + chunks = getChunks>( + groups1, groups2, group, cmpChunkForParentSuperselector); + + // Create expanded array by flattening chunks2 inner + std::vector> + expanded = flattenInner(chunks); + + // Prepare data structures + choices.push_back(expanded); + choices.push_back({ group }); + if (!groups1.empty()) { + groups1.erase(groups1.begin()); + } + if (!groups2.empty()) { + groups2.erase(groups2.begin()); + } + + } + + // Create junks from groups1 and groups2 + std::vector>> + chunks = getChunks>( + groups1, groups2, {}, cmpChunkForEmptySequence); + + // Append chunks with inner arrays flattened + choices.emplace_back(flattenInner(chunks)); + + // append all trailing selectors to choices + std::move(std::begin(trails), std::end(trails), + std::inserter(choices, std::end(choices))); + + // move all non empty items to the front, then erase the trailing ones + choices.erase(std::remove_if(choices.begin(), choices.end(), checkForEmptyChild + >>), choices.end()); + + // permutate all possible paths through selectors + std::vector> + results = flattenInner(permutate(choices)); + + return results; + + } + // EO weaveParents + + // ########################################################################## + // ########################################################################## + +} diff --git a/src/libsass/src/ast_selectors.cpp b/src/libsass/src/ast_selectors.cpp new file mode 100644 index 000000000..1136d9644 --- /dev/null +++ b/src/libsass/src/ast_selectors.cpp @@ -0,0 +1,1033 @@ +// sass.hpp must go before all system headers to get the +// __EXTENSIONS__ fix on Solaris. +#include "sass.hpp" + +#include "ast.hpp" +#include "permutate.hpp" +#include "util_string.hpp" + +namespace Sass { + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + Selector::Selector(ParserState pstate) + : Expression(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(ParserState pstate, String_Obj c) + : AST_Node(pstate), + contents_(c), + connect_parent_(true), + 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(ParserState pstate, std::string n) + : Selector(pstate), ns_(""), name_(n), has_ns_(false) + { + size_t pos = n.find('|'); + // found some namespace + if (pos != std::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_) + { } + + std::string SimpleSelector::ns_name() const + { + if (!has_ns_) return name_; + else return ns_ + "|" + name_; + } + + size_t SimpleSelector::hash() const + { + if (hash_ == 0) { + 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 + { + return has_ns_ && ns_ == "*"; + } + + bool SimpleSelector::is_empty_ns() const + { + return !has_ns_ || ns_ == ""; + } + + bool SimpleSelector::has_empty_ns() const + { + return has_ns_ && ns_ == ""; + } + + bool SimpleSelector::has_qualified_ns() const + { + return has_ns_ && ns_ != "" && ns_ != "*"; + } + + // name query functions + bool SimpleSelector::is_universal() const + { + return name_ == "*"; + } + + bool SimpleSelector::has_placeholder() + { + return false; + } + + bool SimpleSelector::has_real_parent_ref() const + { + return false; + }; + + bool SimpleSelector::is_pseudo_element() const + { + 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; + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + Placeholder_Selector::Placeholder_Selector(ParserState pstate, std::string n) + : SimpleSelector(pstate, n) + { simple_type(PLACEHOLDER_SEL); } + Placeholder_Selector::Placeholder_Selector(const Placeholder_Selector* ptr) + : SimpleSelector(ptr) + { simple_type(PLACEHOLDER_SEL); } + unsigned long Placeholder_Selector::specificity() const + { + return Constants::Specificity_Base; + } + bool Placeholder_Selector::has_placeholder() { + return true; + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + Type_Selector::Type_Selector(ParserState pstate, std::string n) + : SimpleSelector(pstate, n) + { simple_type(TYPE_SEL); } + Type_Selector::Type_Selector(const Type_Selector* ptr) + : SimpleSelector(ptr) + { simple_type(TYPE_SEL); } + + unsigned long Type_Selector::specificity() const + { + if (name() == "*") return 0; + else return Constants::Specificity_Element; + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + Class_Selector::Class_Selector(ParserState pstate, std::string n) + : SimpleSelector(pstate, n) + { simple_type(CLASS_SEL); } + Class_Selector::Class_Selector(const Class_Selector* ptr) + : SimpleSelector(ptr) + { simple_type(CLASS_SEL); } + + unsigned long Class_Selector::specificity() const + { + return Constants::Specificity_Class; + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + Id_Selector::Id_Selector(ParserState pstate, std::string n) + : SimpleSelector(pstate, n) + { simple_type(ID_SEL); } + Id_Selector::Id_Selector(const Id_Selector* ptr) + : SimpleSelector(ptr) + { simple_type(ID_SEL); } + + unsigned long Id_Selector::specificity() const + { + return Constants::Specificity_ID; + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + Attribute_Selector::Attribute_Selector(ParserState pstate, std::string n, std::string m, String_Obj v, char o) + : SimpleSelector(pstate, n), matcher_(m), value_(v), modifier_(o) + { simple_type(ATTRIBUTE_SEL); } + Attribute_Selector::Attribute_Selector(const Attribute_Selector* ptr) + : SimpleSelector(ptr), + matcher_(ptr->matcher_), + value_(ptr->value_), + modifier_(ptr->modifier_) + { simple_type(ATTRIBUTE_SEL); } + + size_t Attribute_Selector::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 Attribute_Selector::specificity() const + { + return Constants::Specificity_Attr; + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + Pseudo_Selector::Pseudo_Selector(ParserState pstate, std::string name, bool element) + : SimpleSelector(pstate, name), + normalized_(Util::unvendor(name)), + argument_({}), + selector_({}), + isSyntacticClass_(!element), + isClass_(!element && !isFakePseudoElement(normalized_)) + { simple_type(PSEUDO_SEL); } + Pseudo_Selector::Pseudo_Selector(const Pseudo_Selector* ptr) + : 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 Pseudo_Selector::is_pseudo_element() const + { + return isElement(); + } + + size_t Pseudo_Selector::hash() const + { + if (hash_ == 0) { + hash_combine(hash_, SimpleSelector::hash()); + if (selector_) hash_combine(hash_, selector_->hash()); + if (argument_) hash_combine(hash_, argument_->hash()); + } + return hash_; + } + + unsigned long Pseudo_Selector::specificity() const + { + if (is_pseudo_element()) + return Constants::Specificity_Element; + return Constants::Specificity_Pseudo; + } + + Pseudo_Selector_Obj Pseudo_Selector::withSelector(SelectorListObj selector) + { + Pseudo_Selector_Obj pseudo = SASS_MEMORY_COPY(this); + pseudo->selector(selector); + return pseudo; + } + + bool Pseudo_Selector::empty() const + { + // Only considered empty if selector is + // available but has no items in it. + return selector() && selector()->empty(); + } + + void Pseudo_Selector::cloneChildren() + { + if (selector().isNull()) selector({}); + else selector(SASS_MEMORY_CLONE(selector())); + } + + bool Pseudo_Selector::has_real_parent_ref() const { + if (!selector()) return false; + return selector()->has_real_parent_ref(); + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + SelectorList::SelectorList(ParserState 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; + } + + void SelectorList::cloneChildren() + { + for (size_t i = 0, l = length(); i < l; i++) { + at(i) = SASS_MEMORY_CLONE(at(i)); + } + } + + 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; + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + ComplexSelector::ComplexSelector(ParserState pstate) + : Selector(pstate), + Vectorized(), + chroots_(false), + hasPreLineFeed_(false) + { + } + ComplexSelector::ComplexSelector(const ComplexSelector* ptr) + : Selector(ptr), + Vectorized(ptr->elements()), + chroots_(ptr->chroots()), + hasPreLineFeed_(ptr->hasPreLineFeed()) + { + } + + void ComplexSelector::cloneChildren() + { + for (size_t i = 0, l = length(); i < l; i++) { + at(i) = SASS_MEMORY_CLONE(at(i)); + } + } + + unsigned long ComplexSelector::specificity() const + { + int sum = 0; + for (auto component : elements()) { + sum += component->specificity(); + } + return sum; + } + + bool ComplexSelector::isInvisible() 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; + } + } + return false; + } + + SelectorListObj ComplexSelector::wrapInList() + { + SelectorListObj selector = + SASS_MEMORY_NEW(SelectorList, pstate()); + selector->append(this); + return selector; + } + + 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_); + } + 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 + { + for (auto item : elements()) { + if (item->has_real_parent_ref()) return true; + } + return false; + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + SelectorComponent::SelectorComponent(ParserState pstate, bool postLineBreak) + : Selector(pstate), + hasPostLineBreak_(postLineBreak) + { + } + + 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; + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + SelectorCombinator::SelectorCombinator(ParserState pstate, SelectorCombinator::Combinator combinator, bool postLineBreak) + : SelectorComponent(pstate, postLineBreak), + combinator_(combinator) + { + } + SelectorCombinator::SelectorCombinator(const SelectorCombinator* ptr) + : SelectorComponent(ptr->pstate(), false), + combinator_(ptr->combinator()) + { } + + void SelectorCombinator::cloneChildren() + { + } + + unsigned long SelectorCombinator::specificity() const + { + return 0; + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + CompoundSelector::CompoundSelector(ParserState pstate, bool postLineBreak) + : SelectorComponent(pstate, postLineBreak), + Vectorized(), + hasRealParent_(false), + extended_(false) + { + } + CompoundSelector::CompoundSelector(const CompoundSelector* ptr) + : SelectorComponent(ptr), + Vectorized(*ptr), + hasRealParent_(ptr->hasRealParent()), + extended_(ptr->extended()) + { } + + size_t CompoundSelector::hash() const + { + if (Selector::hash_ == 0) { + hash_combine(Selector::hash_, Vectorized::hash()); + hash_combine(Selector::hash_, hasRealParent_); + } + return Selector::hash_; + } + + bool CompoundSelector::has_real_parent_ref() const + { + if (hasRealParent()) return true; + // ToDo: dart sass has another check? + // if (Cast(front)) { + // if (front->ns() != "") return false; + // } + for (const SimpleSelector* s : elements()) { + if (s && s->has_real_parent_ref()) return true; + } + return false; + } + + bool CompoundSelector::has_placeholder() const + { + if (length() == 0) return false; + for (SimpleSelectorObj ss : elements()) { + if (ss->has_placeholder()) return true; + } + return false; + } + + void CompoundSelector::cloneChildren() + { + for (size_t i = 0, l = length(); i < l; i++) { + at(i) = SASS_MEMORY_CLONE(at(i)); + } + } + + 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 + { + for (size_t i = 0; i < length(); i += 1) { + if (!get(i)->isInvisible()) return false; + } + return true; + } + + bool CompoundSelector::isSuperselectorOf(const CompoundSelector* sub, std::string wrapped) const + { + CompoundSelector* rhs2 = const_cast(sub); + CompoundSelector* lhs2 = const_cast(this); + return compoundIsSuperselector(lhs2, rhs2, {}); + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + MediaRule::MediaRule(ParserState pstate, Block_Obj block) : + Has_Block(pstate, block), + schema_({}) + { + statement_type(MEDIA); + } + + MediaRule::MediaRule(const MediaRule* ptr) : + Has_Block(ptr), + schema_(ptr->schema_) + { + statement_type(MEDIA); + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + CssMediaRule::CssMediaRule(ParserState pstate, Block_Obj block) : + Has_Block(pstate, block), + Vectorized() + { + statement_type(MEDIA); + } + + CssMediaRule::CssMediaRule(const CssMediaRule* ptr) : + Has_Block(ptr), + Vectorized(*ptr) + { + statement_type(MEDIA); + } + + CssMediaQuery::CssMediaQuery(ParserState pstate) : + AST_Node(pstate), + modifier_(""), + type_(""), + features_() + { + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + 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) + { + + std::string ourType = this->type(); + Util::ascii_str_tolower(&ourType); + + std::string theirType = other->type(); + Util::ascii_str_tolower(&theirType); + + std::string ourModifier = this->modifier(); + Util::ascii_str_tolower(&ourModifier); + + std::string theirModifier = other->modifier(); + Util::ascii_str_tolower(&theirModifier); + + std::string type; + std::string modifier; + std::vector features; + + if (ourType.empty() && theirType.empty()) { + CssMediaQuery_Obj query = SASS_MEMORY_NEW(CssMediaQuery, pstate()); + std::vector f1(this->features()); + std::vector f2(other->features()); + features.insert(features.end(), f1.begin(), f1.end()); + features.insert(features.end(), f2.begin(), f2.end()); + query->features(features); + return query; + } + + if ((ourModifier == "not") != (theirModifier == "not")) { + if (ourType == theirType) { + std::vector negativeFeatures = + ourModifier == "not" ? this->features() : other->features(); + std::vector positiveFeatures = + ourModifier == "not" ? other->features() : this->features(); + + // If the negative features are a subset of the positive features, the + // query is empty. For example, `not screen and (color)` has no + // intersection with `screen and (color) and (grid)`. + // However, `not screen and (color)` *does* intersect with `screen and + // (grid)`, because it means `not (screen and (color))` and so it allows + // a screen with no color but with a grid. + if (listIsSubsetOrEqual(negativeFeatures, positiveFeatures)) { + return SASS_MEMORY_NEW(CssMediaQuery, pstate()); + } + 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; + std::vector f1(this->features()); + std::vector f2(other->features()); + features.insert(features.end(), f1.begin(), f1.end()); + features.insert(features.end(), f2.begin(), f2.end()); + } + else if (other->matchesAllTypes()) { + modifier = ourModifier; + type = ourType; + std::vector f1(this->features()); + std::vector f2(other->features()); + features.insert(features.end(), f1.begin(), f1.end()); + features.insert(features.end(), f2.begin(), f2.end()); + } + else if (ourType != theirType) { + return SASS_MEMORY_NEW(CssMediaQuery, pstate()); + } + else { + modifier = ourModifier.empty() ? theirModifier : ourModifier; + type = ourType; + std::vector f1(this->features()); + std::vector f2(other->features()); + features.insert(features.end(), f1.begin(), f1.end()); + features.insert(features.end(), f2.begin(), f2.end()); + } + } + + 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 specificity = 0; + for (auto complex : elements()) { + specificity = std::max(specificity, complex->maxSpecificity()); + } + return specificity; + } + + size_t SelectorList::minSpecificity() const + { + size_t specificity = 0; + for (auto complex : elements()) { + specificity = std::min(specificity, complex->minSpecificity()); + } + return specificity; + } + + size_t CompoundSelector::maxSpecificity() const + { + size_t specificity = 0; + for (auto simple : elements()) { + specificity += simple->maxSpecificity(); + } + return specificity; + } + + size_t CompoundSelector::minSpecificity() const + { + size_t specificity = 0; + for (auto simple : elements()) { + specificity += simple->minSpecificity(); + } + return specificity; + } + + size_t ComplexSelector::maxSpecificity() const + { + size_t specificity = 0; + for (auto component : elements()) { + specificity += component->maxSpecificity(); + } + return specificity; + } + + size_t ComplexSelector::minSpecificity() const + { + size_t specificity = 0; + for (auto component : elements()) { + specificity += component->minSpecificity(); + } + return specificity; + } + + ///////////////////////////////////////////////////////////////////////// + // ToDo: this might be done easier with new selector format + ///////////////////////////////////////////////////////////////////////// + + std::vector + CompoundSelector::resolve_parent_refs(SelectorStack pstack, Backtraces& traces, bool implicit_parent) + { + + auto parent = pstack.back(); + std::vector rv; + + for (SimpleSelectorObj simple : elements()) { + if (Pseudo_Selector * pseudo = Cast(simple)) { + if (SelectorList* sel = Cast(pseudo->selector())) { + if (parent) { + pseudo->selector(sel->resolve_parent_refs( + pstack, traces, implicit_parent)); + } + } + } + } + + // 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); + } + } + else { + 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); + } + } + } + } + + // No parents + else { + // Create a new wrapper to wrap ourself + auto complex = SASS_MEMORY_NEW(ComplexSelector, pstate()); + // Just append ourself + complex->append(this); + // Append to results + rv.push_back(complex); + } + + return rv; + + } + + /* better return std::vector? only - is empty container anyway? */ + SelectorList* ComplexSelector::resolve_parent_refs(SelectorStack pstack, Backtraces& traces, bool implicit_parent) + { + + std::vector> vars; + + auto parent = pstack.back(); + + if (has_real_parent_ref() && !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; + } + + vars.push_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); + } + else { + // ToDo: merge together sequences whenever possible + auto cont = SASS_MEMORY_NEW(ComplexSelector, pstate()); + cont->append(sel); + vars.push_back({ cont }); + } + } + + // Need complex selectors to preserve linefeeds + std::vector> res = permutateAlt(vars); + + // std::reverse(std::begin(res), std::end(res)); + + auto lst = SASS_MEMORY_NEW(SelectorList, pstate()); + for (auto items : res) { + if (items.size() > 0) { + ComplexSelectorObj first = SASS_MEMORY_COPY(items[0]); + first->hasPreLineFeed(first->hasPreLineFeed() || (!has_real_parent_ref() && hasPreLineFeed())); + // ToDo: remove once we know how to handle line feeds + // ToDo: currently a mashup between ruby and dart sass + // if (has_real_parent_ref()) first->has_line_feed(false); + // first->has_line_break(first->has_line_break() || has_line_break()); + first->chroots(true); // has been resolved by now + for (size_t i = 1; i < items.size(); i += 1) { + first->concat(items[i]); + } + lst->append(first); + } + } + + return lst; + + } + + SelectorList* SelectorList::resolve_parent_refs(SelectorStack pstack, 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); + } + return rv; + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + IMPLEMENT_AST_OPERATORS(Selector_Schema); + IMPLEMENT_AST_OPERATORS(Placeholder_Selector); + IMPLEMENT_AST_OPERATORS(Attribute_Selector); + IMPLEMENT_AST_OPERATORS(Type_Selector); + IMPLEMENT_AST_OPERATORS(Class_Selector); + IMPLEMENT_AST_OPERATORS(Id_Selector); + IMPLEMENT_AST_OPERATORS(Pseudo_Selector); + IMPLEMENT_AST_OPERATORS(SelectorCombinator); + IMPLEMENT_AST_OPERATORS(CompoundSelector); + IMPLEMENT_AST_OPERATORS(ComplexSelector); + IMPLEMENT_AST_OPERATORS(SelectorList); + +} diff --git a/src/libsass/src/ast_selectors.hpp b/src/libsass/src/ast_selectors.hpp new file mode 100644 index 000000000..94eb47105 --- /dev/null +++ b/src/libsass/src/ast_selectors.hpp @@ -0,0 +1,512 @@ +#ifndef SASS_AST_SEL_H +#define SASS_AST_SEL_H + +// sass.hpp must go before all system headers to get the +// __EXTENSIONS__ fix on Solaris. +#include "sass.hpp" +#include "ast.hpp" + +namespace Sass { + + ///////////////////////////////////////////////////////////////////////// + // Some helper functions + ///////////////////////////////////////////////////////////////////////// + + bool compoundIsSuperselector( + const CompoundSelectorObj& compound1, + const CompoundSelectorObj& compound2, + const std::vector& parents); + + bool complexIsParentSuperselector( + const std::vector& complex1, + const std::vector& complex2); + + std::vector> weave( + const std::vector>& complexes); + + std::vector> weaveParents( + std::vector parents1, + std::vector parents2); + + std::vector unifyCompound( + const std::vector& compound1, + const std::vector& compound2); + + std::vector> unifyComplex( + const std::vector>& complexes); + + ///////////////////////////////////////// + // Abstract base class for CSS selectors. + ///////////////////////////////////////// + class Selector : public Expression { + protected: + mutable size_t hash_; + public: + Selector(ParserState 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 + 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) + }; + inline Selector::~Selector() { } + + ///////////////////////////////////////////////////////////////////////// + // Interpolated selectors -- the interpolated String will be expanded and + // re-parsed into a normal selector class. + ///////////////////////////////////////////////////////////////////////// + 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_; + public: + Selector_Schema(ParserState 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() + }; + + //////////////////////////////////////////// + // 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(std::string, ns) + HASH_CONSTREF(std::string, name) + ADD_PROPERTY(Simple_Type, simple_type) + HASH_PROPERTY(bool, has_ns) + public: + SimpleSelector(ParserState pstate, std::string n = ""); + virtual std::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 Id_Selector* getIdSelector() { return NULL; } + virtual Type_Selector* getTypeSelector() { return NULL; } + virtual Pseudo_Selector* getPseudoSelector() { return NULL; } + + ComplexSelectorObj wrapInComplex(); + CompoundSelectorObj wrapInCompound(); + + virtual bool isInvisible() const { return false; } + virtual bool is_pseudo_element() const; + virtual bool has_real_parent_ref() const override; + + bool operator==(const Selector& rhs) const final override; + + virtual bool operator==(const SelectorList& rhs) const; + virtual bool operator==(const ComplexSelector& rhs) const; + virtual bool operator==(const CompoundSelector& rhs) const; + + ATTACH_VIRTUAL_CMP_OPERATIONS(SimpleSelector); + ATTACH_VIRTUAL_AST_OPERATIONS(SimpleSelector); + ATTACH_CRTP_PERFORM_METHODS(); + + }; + inline SimpleSelector::~SimpleSelector() { } + + ///////////////////////////////////////////////////////////////////////// + // Placeholder selectors (e.g., "%foo") for use in extend-only selectors. + ///////////////////////////////////////////////////////////////////////// + class Placeholder_Selector final : public SimpleSelector { + public: + Placeholder_Selector(ParserState pstate, std::string n); + 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(Placeholder_Selector) + ATTACH_AST_OPERATIONS(Placeholder_Selector) + ATTACH_CRTP_PERFORM_METHODS() + }; + + ///////////////////////////////////////////////////////////////////// + // Type selectors (and the universal selector) -- e.g., div, span, *. + ///////////////////////////////////////////////////////////////////// + class Type_Selector final : public SimpleSelector { + public: + Type_Selector(ParserState pstate, std::string n); + virtual unsigned long specificity() const override; + SimpleSelector* unifyWith(const SimpleSelector*); + CompoundSelector* unifyWith(CompoundSelector*) override; + Type_Selector* getTypeSelector() override { return this; } + bool operator==(const SimpleSelector& rhs) const final override; + ATTACH_CMP_OPERATIONS(Type_Selector) + ATTACH_AST_OPERATIONS(Type_Selector) + ATTACH_CRTP_PERFORM_METHODS() + }; + + //////////////////////////////////////////////// + // Class selectors -- i.e., .foo. + //////////////////////////////////////////////// + class Class_Selector final : public SimpleSelector { + public: + Class_Selector(ParserState pstate, std::string n); + virtual unsigned long specificity() const override; + bool operator==(const SimpleSelector& rhs) const final override; + ATTACH_CMP_OPERATIONS(Class_Selector) + ATTACH_AST_OPERATIONS(Class_Selector) + ATTACH_CRTP_PERFORM_METHODS() + }; + + //////////////////////////////////////////////// + // ID selectors -- i.e., #foo. + //////////////////////////////////////////////// + class Id_Selector final : public SimpleSelector { + public: + Id_Selector(ParserState pstate, std::string n); + virtual unsigned long specificity() const override; + CompoundSelector* unifyWith(CompoundSelector*) override; + Id_Selector* getIdSelector() final override { return this; } + bool operator==(const SimpleSelector& rhs) const final override; + ATTACH_CMP_OPERATIONS(Id_Selector) + ATTACH_AST_OPERATIONS(Id_Selector) + ATTACH_CRTP_PERFORM_METHODS() + }; + + /////////////////////////////////////////////////// + // Attribute selectors -- e.g., [src*=".jpg"], etc. + /////////////////////////////////////////////////// + class Attribute_Selector final : public SimpleSelector { + ADD_CONSTREF(std::string, matcher) + // this cannot be changed to obj atm!!!!!!????!!!!!!! + ADD_PROPERTY(String_Obj, value) // might be interpolated + ADD_PROPERTY(char, modifier); + public: + Attribute_Selector(ParserState pstate, std::string n, std::string m, String_Obj v, char o = 0); + size_t hash() const override; + virtual unsigned long specificity() const override; + bool operator==(const SimpleSelector& rhs) const final override; + ATTACH_CMP_OPERATIONS(Attribute_Selector) + ATTACH_AST_OPERATIONS(Attribute_Selector) + ATTACH_CRTP_PERFORM_METHODS() + }; + + ////////////////////////////////////////////////////////////////// + // Pseudo selectors -- e.g., :first-child, :nth-of-type(...), etc. + ////////////////////////////////////////////////////////////////// + // Pseudo Selector cannot have any namespace? + class Pseudo_Selector final : public SimpleSelector { + ADD_PROPERTY(std::string, normalized) + ADD_PROPERTY(String_Obj, argument) + ADD_PROPERTY(SelectorListObj, selector) + ADD_PROPERTY(bool, isSyntacticClass) + ADD_PROPERTY(bool, isClass) + public: + Pseudo_Selector(ParserState pstate, std::string n, bool element = false); + virtual bool is_pseudo_element() const override; + size_t hash() const override; + + bool empty() const override; + + bool has_real_parent_ref() const override; + + // Whether this is a pseudo-element selector. + // This is `true` if and only if [isClass] is `false`. + bool isElement() const { return !isClass(); } + + // Whether this is syntactically a pseudo-element selector. + // This is `true` if and only if [isSyntacticClass] is `false`. + bool isSyntacticElement() const { return !isSyntacticClass(); } + + virtual unsigned long specificity() const override; + Pseudo_Selector_Obj withSelector(SelectorListObj selector); + + CompoundSelector* unifyWith(CompoundSelector*) override; + Pseudo_Selector* getPseudoSelector() final override { return this; } + bool operator==(const SimpleSelector& rhs) const final override; + ATTACH_CMP_OPERATIONS(Pseudo_Selector) + ATTACH_AST_OPERATIONS(Pseudo_Selector) + void cloneChildren() override; + ATTACH_CRTP_PERFORM_METHODS() + }; + + + //////////////////////////////////////////////////////////////////////////// + // Complex Selectors are the most important class of selectors. + // A Selector List consists of Complex Selectors (separated by comma) + // Complex Selectors are itself a list of Compounds and Combinators + // Between each item there is an implicit ancestor of combinator + //////////////////////////////////////////////////////////////////////////// + class ComplexSelector final : public Selector, public Vectorized { + ADD_PROPERTY(bool, chroots) + // line break before list separator + ADD_PROPERTY(bool, hasPreLineFeed) + public: + ComplexSelector(ParserState pstate); + + // Returns true if the first components + // is a compound selector and fullfills + // a few other criteria. + bool isInvisible() const; + + size_t hash() const override; + void cloneChildren() override; + bool has_placeholder() const; + bool has_real_parent_ref() const override; + + SelectorList* resolve_parent_refs(SelectorStack pstack, Backtraces& traces, bool implicit_parent = true); + virtual unsigned long specificity() const override; + + SelectorList* unifyWith(ComplexSelector* rhs); + + bool isSuperselectorOf(const ComplexSelector* sub) const; + + SelectorListObj wrapInList(); + + size_t maxSpecificity() const override; + size_t minSpecificity() const override; + + bool operator==(const Selector& rhs) const override; + bool operator==(const SelectorList& rhs) const; + bool operator==(const CompoundSelector& rhs) const; + bool operator==(const SimpleSelector& rhs) const; + + ATTACH_CMP_OPERATIONS(ComplexSelector) + ATTACH_AST_OPERATIONS(ComplexSelector) + ATTACH_CRTP_PERFORM_METHODS() + }; + + //////////////////////////////////////////////////////////////////////////// + // Base class for complex selector components + //////////////////////////////////////////////////////////////////////////// + class SelectorComponent : public Selector { + // line break after list separator + ADD_PROPERTY(bool, hasPostLineBreak) + public: + SelectorComponent(ParserState pstate, bool postLineBreak = false); + size_t hash() const override = 0; + void cloneChildren() 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(); + + size_t maxSpecificity() const override { return 0; } + size_t minSpecificity() const override { return 0; } + + virtual bool isCompound() const { return false; }; + virtual bool isCombinator() const { return false; }; + + /* helper function for syntax sugar */ + virtual CompoundSelector* getCompound() { return NULL; } + virtual SelectorCombinator* getCombinator() { return NULL; } + virtual const CompoundSelector* getCompound() const { return NULL; } + virtual const SelectorCombinator* getCombinator() const { return NULL; } + + virtual unsigned long specificity() const override; + bool operator==(const Selector& rhs) const override = 0; + ATTACH_VIRTUAL_CMP_OPERATIONS(SelectorComponent); + ATTACH_VIRTUAL_AST_OPERATIONS(SelectorComponent); + }; + + //////////////////////////////////////////////////////////////////////////// + // A specific combinator between compound selectors + //////////////////////////////////////////////////////////////////////////// + class SelectorCombinator final : public SelectorComponent { + public: + + // Enumerate all possible selector combinators. There is some + // discrepancy with dart-sass. Opted to name them as in CSS33 + enum Combinator { CHILD /* > */, GENERAL /* ~ */, ADJACENT /* + */}; + + private: + + // Store the type of this combinator + HASH_CONSTREF(Combinator, combinator) + + public: + SelectorCombinator(ParserState 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; } + + // Query type of combinator + bool isCombinator() const override { return true; }; + + // Matches the right-hand selector if it's a direct child of the left- + // hand selector in the DOM tree. Dart-sass also calls this `child` + // https://developer.mozilla.org/en-US/docs/Web/CSS/Child_combinator + bool isChildCombinator() const { return combinator_ == CHILD; } // > + + // Matches the right-hand selector if it comes after the left-hand + // selector in the DOM tree. Dart-sass class this `followingSibling` + // https://developer.mozilla.org/en-US/docs/Web/CSS/General_sibling_combinator + bool isGeneralCombinator() const { return combinator_ == GENERAL; } // ~ + + // Matches the right-hand selector if it's immediately adjacent to the + // left-hand selector in the DOM tree. Dart-sass calls this `nextSibling` + // https://developer.mozilla.org/en-US/docs/Web/CSS/Adjacent_sibling_combinator + bool isAdjacentCombinator() const { return combinator_ == ADJACENT; } // + + + size_t maxSpecificity() const override { return 0; } + size_t minSpecificity() const override { 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() + }; + + //////////////////////////////////////////////////////////////////////////// + // A compound selector consists of multiple simple selectors + //////////////////////////////////////////////////////////////////////////// + class CompoundSelector final : public SelectorComponent, public Vectorized { + ADD_PROPERTY(bool, hasRealParent) + ADD_PROPERTY(bool, extended) + public: + CompoundSelector(ParserState pstate, bool postLineBreak = false); + + // Returns true if this compound selector + // fullfills various criteria. + bool isInvisible() const; + + bool empty() const override { + return Vectorized::empty(); + } + + size_t hash() const override; + CompoundSelector* unifyWith(CompoundSelector* rhs); + + /* helper function for syntax sugar */ + CompoundSelector* getCompound() final override { return this; } + const CompoundSelector* getCompound() const final override { return this; } + + bool isSuperselectorOf(const CompoundSelector* sub, std::string wrapped = "") const; + + void cloneChildren() override; + bool has_real_parent_ref() const override; + bool has_placeholder() const override; + std::vector resolve_parent_refs(SelectorStack pstack, Backtraces& traces, bool implicit_parent = true); + + virtual bool isCompound() const override { return true; }; + virtual unsigned long specificity() const override; + + size_t maxSpecificity() const override; + size_t minSpecificity() const override; + + bool operator==(const Selector& rhs) const override; + + bool operator==(const SelectorComponent& rhs) const override; + + bool operator==(const SelectorList& rhs) const; + bool operator==(const ComplexSelector& rhs) const; + bool operator==(const SimpleSelector& rhs) const; + + ATTACH_CMP_OPERATIONS(CompoundSelector) + ATTACH_AST_OPERATIONS(CompoundSelector) + ATTACH_CRTP_PERFORM_METHODS() + }; + + /////////////////////////////////// + // Comma-separated selector groups. + /////////////////////////////////// + class SelectorList final : public Selector, public Vectorized { + private: + // maybe we have optional flag + // ToDo: should be at ExtendRule? + ADD_PROPERTY(bool, is_optional) + public: + SelectorList(ParserState pstate, size_t s = 0); + std::string type() const override { return "list"; } + size_t hash() const override; + + SelectorList* unifyWith(SelectorList*); + + // Returns true if all complex selectors + // can have real parents, meaning every + // first component does allow for it + bool isInvisible() const; + + void cloneChildren() override; + bool has_real_parent_ref() const override; + SelectorList* resolve_parent_refs(SelectorStack pstack, Backtraces& traces, bool implicit_parent = true); + virtual unsigned long specificity() const override; + + bool isSuperselectorOf(const SelectorList* sub) const; + + size_t maxSpecificity() const override; + size_t minSpecificity() const override; + + bool operator==(const Selector& rhs) const override; + bool operator==(const 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; + + ATTACH_CMP_OPERATIONS(SelectorList) + ATTACH_AST_OPERATIONS(SelectorList) + ATTACH_CRTP_PERFORM_METHODS() + }; + + //////////////////////////////// + // The Sass `@extend` directive. + //////////////////////////////// + class ExtendRule final : public Statement { + ADD_PROPERTY(bool, isOptional) + // This should be a simple selector only! + ADD_PROPERTY(SelectorListObj, selector) + ADD_PROPERTY(Selector_Schema_Obj, schema) + public: + ExtendRule(ParserState pstate, SelectorListObj s); + ExtendRule(ParserState pstate, Selector_Schema_Obj s); + ATTACH_AST_OPERATIONS(ExtendRule) + ATTACH_CRTP_PERFORM_METHODS() + }; + +} + +#endif diff --git a/src/libsass/src/ast_supports.cpp b/src/libsass/src/ast_supports.cpp new file mode 100644 index 000000000..c2a1d095c --- /dev/null +++ b/src/libsass/src/ast_supports.cpp @@ -0,0 +1,114 @@ +// sass.hpp must go before all system headers to get the +// __EXTENSIONS__ fix on Solaris. +#include "sass.hpp" +#include "ast.hpp" + + +namespace Sass { + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + Supports_Block::Supports_Block(ParserState pstate, Supports_Condition_Obj condition, Block_Obj block) + : Has_Block(pstate, block), condition_(condition) + { statement_type(SUPPORTS); } + Supports_Block::Supports_Block(const Supports_Block* ptr) + : Has_Block(ptr), condition_(ptr->condition_) + { statement_type(SUPPORTS); } + bool Supports_Block::bubbles() { return true; } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + Supports_Condition::Supports_Condition(ParserState pstate) + : Expression(pstate) + { } + + Supports_Condition::Supports_Condition(const Supports_Condition* ptr) + : Expression(ptr) + { } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + Supports_Operator::Supports_Operator(ParserState pstate, Supports_Condition_Obj l, Supports_Condition_Obj r, Operand o) + : Supports_Condition(pstate), left_(l), right_(r), operand_(o) + { } + Supports_Operator::Supports_Operator(const Supports_Operator* ptr) + : Supports_Condition(ptr), + left_(ptr->left_), + right_(ptr->right_), + operand_(ptr->operand_) + { } + + bool Supports_Operator::needs_parens(Supports_Condition_Obj cond) const + { + if (Supports_Operator_Obj op = Cast(cond)) { + return op->operand() != operand(); + } + return Cast(cond) != NULL; + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + Supports_Negation::Supports_Negation(ParserState pstate, Supports_Condition_Obj c) + : Supports_Condition(pstate), condition_(c) + { } + Supports_Negation::Supports_Negation(const Supports_Negation* ptr) + : Supports_Condition(ptr), condition_(ptr->condition_) + { } + + bool Supports_Negation::needs_parens(Supports_Condition_Obj cond) const + { + return Cast(cond) || + Cast(cond); + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + Supports_Declaration::Supports_Declaration(ParserState pstate, Expression_Obj f, Expression_Obj v) + : Supports_Condition(pstate), feature_(f), value_(v) + { } + Supports_Declaration::Supports_Declaration(const Supports_Declaration* ptr) + : Supports_Condition(ptr), + feature_(ptr->feature_), + value_(ptr->value_) + { } + + bool Supports_Declaration::needs_parens(Supports_Condition_Obj cond) const + { + return false; + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + Supports_Interpolation::Supports_Interpolation(ParserState pstate, Expression_Obj v) + : Supports_Condition(pstate), value_(v) + { } + Supports_Interpolation::Supports_Interpolation(const Supports_Interpolation* ptr) + : Supports_Condition(ptr), + value_(ptr->value_) + { } + + bool Supports_Interpolation::needs_parens(Supports_Condition_Obj cond) const + { + return false; + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + IMPLEMENT_AST_OPERATORS(Supports_Block); + IMPLEMENT_AST_OPERATORS(Supports_Condition); + IMPLEMENT_AST_OPERATORS(Supports_Operator); + IMPLEMENT_AST_OPERATORS(Supports_Negation); + IMPLEMENT_AST_OPERATORS(Supports_Declaration); + IMPLEMENT_AST_OPERATORS(Supports_Interpolation); + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + +} diff --git a/src/libsass/src/ast_supports.hpp b/src/libsass/src/ast_supports.hpp new file mode 100644 index 000000000..506d3a3b6 --- /dev/null +++ b/src/libsass/src/ast_supports.hpp @@ -0,0 +1,121 @@ +#ifndef SASS_AST_SUPPORTS_H +#define SASS_AST_SUPPORTS_H + +// sass.hpp must go before all system headers to get the +// __EXTENSIONS__ fix on Solaris. +#include "sass.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include "sass/base.h" +#include "ast_fwd_decl.hpp" + +#include "util.hpp" +#include "units.hpp" +#include "context.hpp" +#include "position.hpp" +#include "constants.hpp" +#include "operation.hpp" +#include "position.hpp" +#include "inspect.hpp" +#include "source_map.hpp" +#include "environment.hpp" +#include "error_handling.hpp" +#include "ast_def_macros.hpp" +#include "ast_fwd_decl.hpp" +#include "source_map.hpp" +#include "fn_utils.hpp" + +#include "sass.h" + +namespace Sass { + + //////////////////// + // `@supports` rule. + //////////////////// + class Supports_Block : public Has_Block { + ADD_PROPERTY(Supports_Condition_Obj, condition) + public: + Supports_Block(ParserState pstate, Supports_Condition_Obj condition, Block_Obj block = {}); + bool bubbles() override; + ATTACH_AST_OPERATIONS(Supports_Block) + ATTACH_CRTP_PERFORM_METHODS() + }; + + ////////////////////////////////////////////////////// + // The abstract superclass of all Supports conditions. + ////////////////////////////////////////////////////// + class Supports_Condition : public Expression { + public: + Supports_Condition(ParserState pstate); + virtual bool needs_parens(Supports_Condition_Obj cond) const { return false; } + ATTACH_AST_OPERATIONS(Supports_Condition) + ATTACH_CRTP_PERFORM_METHODS() + }; + + //////////////////////////////////////////////////////////// + // An operator condition (e.g. `CONDITION1 and CONDITION2`). + //////////////////////////////////////////////////////////// + class Supports_Operator : public Supports_Condition { + public: + enum Operand { AND, OR }; + private: + ADD_PROPERTY(Supports_Condition_Obj, left); + ADD_PROPERTY(Supports_Condition_Obj, right); + ADD_PROPERTY(Operand, operand); + public: + Supports_Operator(ParserState pstate, Supports_Condition_Obj l, Supports_Condition_Obj r, Operand o); + virtual bool needs_parens(Supports_Condition_Obj cond) const override; + ATTACH_AST_OPERATIONS(Supports_Operator) + ATTACH_CRTP_PERFORM_METHODS() + }; + + ////////////////////////////////////////// + // A negation condition (`not CONDITION`). + ////////////////////////////////////////// + class Supports_Negation : public Supports_Condition { + private: + ADD_PROPERTY(Supports_Condition_Obj, condition); + public: + Supports_Negation(ParserState pstate, Supports_Condition_Obj c); + virtual bool needs_parens(Supports_Condition_Obj cond) const override; + ATTACH_AST_OPERATIONS(Supports_Negation) + ATTACH_CRTP_PERFORM_METHODS() + }; + + ///////////////////////////////////////////////////// + // A declaration condition (e.g. `(feature: value)`). + ///////////////////////////////////////////////////// + class Supports_Declaration : public Supports_Condition { + private: + ADD_PROPERTY(Expression_Obj, feature); + ADD_PROPERTY(Expression_Obj, value); + public: + Supports_Declaration(ParserState pstate, Expression_Obj f, Expression_Obj v); + virtual bool needs_parens(Supports_Condition_Obj cond) const override; + ATTACH_AST_OPERATIONS(Supports_Declaration) + ATTACH_CRTP_PERFORM_METHODS() + }; + + /////////////////////////////////////////////// + // An interpolation condition (e.g. `#{$var}`). + /////////////////////////////////////////////// + class Supports_Interpolation : public Supports_Condition { + private: + ADD_PROPERTY(Expression_Obj, value); + public: + Supports_Interpolation(ParserState pstate, Expression_Obj v); + virtual bool needs_parens(Supports_Condition_Obj cond) const override; + ATTACH_AST_OPERATIONS(Supports_Interpolation) + ATTACH_CRTP_PERFORM_METHODS() + }; + +} + +#endif diff --git a/src/libsass/src/ast_values.cpp b/src/libsass/src/ast_values.cpp new file mode 100644 index 000000000..07bc7d8be --- /dev/null +++ b/src/libsass/src/ast_values.cpp @@ -0,0 +1,1154 @@ +// sass.hpp must go before all system headers to get the +// __EXTENSIONS__ fix on Solaris. +#include "sass.hpp" +#include "ast.hpp" + +namespace Sass { + + void str_rtrim(std::string& str, const std::string& delimiters = " \f\n\r\t\v") + { + str.erase( str.find_last_not_of( delimiters ) + 1 ); + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + PreValue::PreValue(ParserState pstate, bool d, bool e, bool i, Type ct) + : Expression(pstate, d, e, i, ct) + { } + PreValue::PreValue(const PreValue* ptr) + : Expression(ptr) + { } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + Value::Value(ParserState pstate, bool d, bool e, bool i, Type ct) + : PreValue(pstate, d, e, i, ct) + { } + Value::Value(const Value* ptr) + : PreValue(ptr) + { } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + List::List(ParserState 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); } + + 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 + { + 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()); + } + return hash_; + } + + void List::set_delayed(bool delayed) + { + is_delayed(delayed); + // don't set children + } + + bool List::operator< (const Expression& rhs) const + { + if (auto r = Cast(&rhs)) { + if (length() < r->length()) return true; + if (length() > r->length()) return false; + const auto& left = elements(); + const auto& right = r->elements(); + for (size_t i = 0; i < left.size(); i += 1) { + if (*left[i] < *right[i]) return true; + if (*left[i] == *right[i]) continue; + return false; + } + return false; + } + // compare/sort by type + return type() < rhs.type(); + } + + bool List::operator== (const Expression& rhs) const + { + if (auto r = Cast(&rhs)) { + 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; + } + 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) { + Expression_Obj obj = this->at(i); + if (Argument* arg = Cast(obj)) { + if (!arg->name().empty()) return i; + } + } + return length(); + } + + + Expression_Obj List::value_at_index(size_t i) { + Expression_Obj obj = this->at(i); + if (is_arglist_) { + if (Argument* arg = Cast(obj)) { + return arg->value(); + } else { + return obj; + } + } else { + return obj; + } + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + Map::Map(ParserState pstate, size_t size) + : Value(pstate), + Hashed(size) + { concrete_type(MAP); } + + 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(); + } + + bool Map::operator== (const Expression& rhs) 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; + } + + List_Obj Map::to_list(ParserState& pstate) { + List_Obj ret = SASS_MEMORY_NEW(List, pstate, length(), SASS_COMMA); + + for (auto key : keys()) { + List_Obj l = SASS_MEMORY_NEW(List, pstate, 2); + l->append(key); + l->append(at(key)); + ret->append(l); + } + + return ret; + } + + size_t Map::hash() const + { + if (hash_ == 0) { + for (auto key : keys()) { + hash_combine(hash_, key->hash()); + hash_combine(hash_, at(key)->hash()); + } + } + + return hash_; + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + Binary_Expression::Binary_Expression(ParserState pstate, + Operand op, Expression_Obj lhs, Expression_Obj rhs) + : PreValue(pstate), op_(op), left_(lhs), right_(rhs), hash_(0) + { } + + 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 + { + return is_interpolant() || (left() && left()->is_left_interpolant()); + } + bool Binary_Expression::is_right_interpolant(void) const + { + return is_interpolant() || (right() && right()->is_right_interpolant()); + } + + const std::string Binary_Expression::type_name() + { + return sass_op_to_name(optype()); + } + + const std::string Binary_Expression::separator() + { + return sass_op_separator(optype()); + } + + bool Binary_Expression::has_interpolant() const + { + return is_left_interpolant() || + is_right_interpolant(); + } + + void Binary_Expression::set_delayed(bool delayed) + { + right()->set_delayed(delayed); + left()->set_delayed(delayed); + is_delayed(delayed); + } + + bool Binary_Expression::operator<(const Expression& rhs) const + { + if (auto m = Cast(&rhs)) { + return type() < m->type() || + *left() < *m->left() || + *right() < *m->right(); + } + // compare/sort by type + return type() < rhs.type(); + } + + bool Binary_Expression::operator==(const Expression& rhs) const + { + if (auto m = Cast(&rhs)) { + return type() == m->type() && + *left() == *m->left() && + *right() == *m->right(); + } + return false; + } + + size_t Binary_Expression::hash() const + { + if (hash_ == 0) { + hash_ = std::hash()(optype()); + hash_combine(hash_, left()->hash()); + hash_combine(hash_, right()->hash()); + } + return hash_; + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + Function::Function(ParserState pstate, Definition_Obj def, bool css) + : Value(pstate), definition_(def), is_css_(css) + { concrete_type(FUNCTION_VAL); } + + 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 + { + if (auto r = Cast(&rhs)) { + auto d1 = Cast(definition()); + auto d2 = Cast(r->definition()); + if (d1 == nullptr) return d2 != nullptr; + else if (d2 == nullptr) return false; + if (is_css() == r->is_css()) { + return d1 < d2; + } + return r->is_css(); + } + // compare/sort by type + return type() < rhs.type(); + } + + bool Function::operator== (const Expression& rhs) const + { + if (auto r = Cast(&rhs)) { + auto d1 = Cast(definition()); + auto d2 = Cast(r->definition()); + return d1 && d2 && d1 == d2 && is_css() == r->is_css(); + } + return false; + } + + std::string Function::name() { + if (definition_) { + return definition_->name(); + } + return ""; + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + Function_Call::Function_Call(ParserState 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(ParserState 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(ParserState 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(ParserState pstate, std::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(ParserState pstate, std::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(ParserState pstate, std::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); } + + 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 + { + 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; + } + return false; + } + + size_t Function_Call::hash() const + { + if (hash_ == 0) { + hash_ = std::hash()(name()); + for (auto argument : arguments()->elements()) + hash_combine(hash_, argument->hash()); + } + return hash_; + } + + std::string Function_Call::name() const + { + return sname(); + } + + bool Function_Call::is_css() { + if (func_) return func_->is_css(); + return false; + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + Variable::Variable(ParserState pstate, std::string n) + : PreValue(pstate), name_(n) + { concrete_type(VARIABLE); } + + Variable::Variable(const Variable* ptr) + : PreValue(ptr), name_(ptr->name_) + { concrete_type(VARIABLE); } + + bool Variable::operator==(const Expression& rhs) const + { + if (auto e = Cast(&rhs)) { + return name() == e->name(); + } + return false; + } + + size_t Variable::hash() const + { + return std::hash()(name()); + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + Number::Number(ParserState pstate, double val, std::string u, bool zero) + : Value(pstate), + Units(), + value_(val), + zero_(zero), + hash_(0) + { + size_t l = 0; + size_t r; + if (!u.empty()) { + bool nominator = true; + while (true) { + r = u.find_first_of("*/", l); + std::string unit(u.substr(l, r == std::string::npos ? r : r - l)); + if (!unit.empty()) { + if (nominator) numerators.push_back(unit); + else denominators.push_back(unit); + } + if (r == std::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); + } + + Number::Number(const Number* ptr) + : Value(ptr), + Units(ptr), + value_(ptr->value_), zero_(ptr->zero_), + hash_(ptr->hash_) + { concrete_type(NUMBER); } + + // cancel out unnecessary units + void Number::reduce() + { + // apply conversion factor + value_ *= this->Units::reduce(); + } + + void Number::normalize() + { + // apply conversion factor + value_ *= this->Units::normalize(); + } + + size_t Number::hash() 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_; + } + + bool Number::operator< (const Expression& rhs) const + { + if (auto n = Cast(&rhs)) { + return *this < *n; + } + return false; + } + + bool Number::operator== (const Expression& rhs) const + { + if (auto n = Cast(&rhs)) { + return *this == *n; + } + 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()); + } + // ensure both have same units + l.normalize(); r.normalize(); + Units &lhs_unit = l, &rhs_unit = r; + return lhs_unit == rhs_unit && + NEAR_EQUAL(l.value(), r.value()); + } + + 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 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; + } + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + Color::Color(ParserState pstate, double a, const std::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 + { + if (auto r = Cast(&rhs)) { + return *this < *r; + } + else if (auto r = Cast(&rhs)) { + return *this < *r; + } + else if (auto r = Cast(&rhs)) { + return a_ < r->a(); + } + // compare/sort by type + return type() < rhs.type(); + } + + bool Color::operator== (const Expression& rhs) const + { + if (auto r = Cast(&rhs)) { + return *this == *r; + } + else if (auto r = Cast(&rhs)) { + return *this == *r; + } + else if (auto r = Cast(&rhs)) { + return a_ == r->a(); + } + return false; + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + Color_RGBA::Color_RGBA(ParserState pstate, double r, double g, double b, double a, const std::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 + { + if (auto r = Cast(&rhs)) { + if (r_ < r->r()) return true; + if (r_ > r->r()) return false; + if (g_ < r->g()) return true; + if (g_ > r->g()) return false; + if (b_ < r->b()) return true; + if (b_ > r->b()) return false; + if (a_ < r->a()) return true; + if (a_ > r->a()) return false; + return false; // is equal + } + // compare/sort by type + return type() < rhs.type(); + } + + bool Color_RGBA::operator== (const Expression& rhs) const + { + if (auto r = Cast(&rhs)) { + return r_ == r->r() && + g_ == r->g() && + b_ == r->b() && + a_ == r->a(); + } + return false; + } + + size_t Color_RGBA::hash() const + { + 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_)); + } + return hash_; + } + + Color_HSLA* Color_RGBA::copyAsHSLA() 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; + + 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(Color_HSLA, + pstate(), h, s, l, a(), "" + ); + } + + Color_RGBA* Color_RGBA::copyAsRGBA() const + { + return SASS_MEMORY_COPY(this); + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + Color_HSLA::Color_HSLA(ParserState pstate, double h, double s, double l, double a, const std::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 + { + if (auto r = Cast(&rhs)) { + if (h_ < r->h()) return true; + if (h_ > r->h()) return false; + if (s_ < r->s()) return true; + if (s_ > r->s()) return false; + if (l_ < r->l()) return true; + if (l_ > r->l()) return false; + if (a_ < r->a()) return true; + if (a_ > r->a()) return false; + return false; // is equal + } + // compare/sort by type + return type() < rhs.type(); + } + + bool Color_HSLA::operator== (const Expression& rhs) const + { + if (auto r = Cast(&rhs)) { + return h_ == r->h() && + s_ == r->s() && + l_ == r->l() && + a_ == r->a(); + } + return false; + } + + size_t Color_HSLA::hash() 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_)); + } + return hash_; + } + + // hue to RGB helper function + double h_to_rgb(double m1, double m2, double h) + { + 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; + } + + 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); + + return SASS_MEMORY_NEW(Color_RGBA, + pstate(), r, g, b, a(), "" + ); + } + + Color_HSLA* Color_HSLA::copyAsHSLA() const + { + return SASS_MEMORY_COPY(this); + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + Custom_Error::Custom_Error(ParserState pstate, std::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 + { + if (auto r = Cast(&rhs)) { + return message() < r->message(); + } + // compare/sort by type + return type() < rhs.type(); + } + + bool Custom_Error::operator== (const Expression& rhs) const + { + if (auto r = Cast(&rhs)) { + return message() == r->message(); + } + return false; + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + Custom_Warning::Custom_Warning(ParserState pstate, std::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); } + + bool Custom_Warning::operator< (const Expression& rhs) const + { + if (auto r = Cast(&rhs)) { + return message() < r->message(); + } + // compare/sort by type + return type() < rhs.type(); + } + + bool Custom_Warning::operator== (const Expression& rhs) const + { + if (auto r = Cast(&rhs)) { + return message() == r->message(); + } + return false; + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + Boolean::Boolean(ParserState pstate, bool val) + : Value(pstate), value_(val), + hash_(0) + { concrete_type(BOOLEAN); } + + Boolean::Boolean(const Boolean* ptr) + : Value(ptr), + value_(ptr->value_), + hash_(ptr->hash_) + { concrete_type(BOOLEAN); } + + bool Boolean::operator< (const Expression& rhs) const + { + if (auto r = Cast(&rhs)) { + return (value() < r->value()); + } + return false; + } + + bool Boolean::operator== (const Expression& rhs) const + { + if (auto r = Cast(&rhs)) { + return (value() == r->value()); + } + return false; + } + + size_t Boolean::hash() const + { + if (hash_ == 0) { + hash_ = std::hash()(value_); + } + return hash_; + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + String::String(ParserState pstate, bool delayed) + : Value(pstate, delayed) + { concrete_type(STRING); } + String::String(const String* ptr) + : Value(ptr) + { concrete_type(STRING); } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + String_Schema::String_Schema(ParserState 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 + { + 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; + } + return false; + } + + bool String_Schema::has_interpolants() + { + for (auto el : elements()) { + if (el->is_interpolant()) return true; + } + return false; + } + + size_t String_Schema::hash() const + { + if (hash_ == 0) { + for (auto string : elements()) + hash_combine(hash_, string->hash()); + } + return hash_; + } + + void String_Schema::set_delayed(bool delayed) + { + is_delayed(delayed); + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + String_Constant::String_Constant(ParserState pstate, std::string val, bool css) + : String(pstate), quote_mark_(0), value_(read_css_string(val, css)), hash_(0) + { } + String_Constant::String_Constant(ParserState pstate, const char* beg, bool css) + : String(pstate), quote_mark_(0), value_(read_css_string(std::string(beg), css)), hash_(0) + { } + String_Constant::String_Constant(ParserState pstate, const char* beg, const char* end, bool css) + : String(pstate), quote_mark_(0), value_(read_css_string(std::string(beg, end-beg), css)), hash_(0) + { } + String_Constant::String_Constant(ParserState pstate, const Token& tok, bool css) + : String(pstate), quote_mark_(0), value_(read_css_string(std::string(tok.begin, tok.end), css)), hash_(0) + { } + + String_Constant::String_Constant(const String_Constant* ptr) + : String(ptr), + quote_mark_(ptr->quote_mark_), + value_(ptr->value_), + hash_(ptr->hash_) + { } + + bool String_Constant::is_invisible() const { + return value_.empty() && quote_mark_ == 0; + } + + bool String_Constant::operator< (const Expression& rhs) const + { + if (auto qstr = Cast(&rhs)) { + return value() < qstr->value(); + } + else if (auto cstr = Cast(&rhs)) { + return value() < cstr->value(); + } + // compare/sort by type + return type() < rhs.type(); + } + + bool String_Constant::operator== (const Expression& rhs) const + { + if (auto qstr = Cast(&rhs)) { + return value() == qstr->value(); + } + else if (auto cstr = Cast(&rhs)) { + return value() == cstr->value(); + } + return false; + } + + std::string String_Constant::inspect() const + { + return quote(value_, '*'); + } + + void String_Constant::rtrim() + { + str_rtrim(value_); + } + + size_t String_Constant::hash() const + { + if (hash_ == 0) { + hash_ = std::hash()(value_); + } + return hash_; + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + String_Quoted::String_Quoted(ParserState pstate, std::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; + } + + String_Quoted::String_Quoted(const String_Quoted* ptr) + : String_Constant(ptr) + { } + + bool String_Quoted::operator< (const Expression& rhs) const + { + if (auto qstr = Cast(&rhs)) { + return value() < qstr->value(); + } + else if (auto cstr = Cast(&rhs)) { + return value() < cstr->value(); + } + // compare/sort by type + return type() < rhs.type(); + } + + bool String_Quoted::operator== (const Expression& rhs) const + { + if (auto qstr = Cast(&rhs)) { + return value() == qstr->value(); + } + else if (auto cstr = Cast(&rhs)) { + return value() == cstr->value(); + } + return false; + } + + std::string String_Quoted::inspect() const + { + return quote(value_, '*'); + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + Null::Null(ParserState pstate) + : Value(pstate) + { concrete_type(NULL_VAL); } + + Null::Null(const Null* ptr) : Value(ptr) + { concrete_type(NULL_VAL); } + + bool Null::operator< (const Expression& rhs) const + { + if (Cast(&rhs)) { + return false; + } + // compare/sort by type + return type() < rhs.type(); + } + + bool Null::operator== (const Expression& rhs) const + { + return Cast(&rhs) != nullptr; + } + + size_t Null::hash() const + { + return -1; + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + Parent_Reference::Parent_Reference(ParserState pstate) + : Value(pstate) + { concrete_type(PARENT); } + + Parent_Reference::Parent_Reference(const Parent_Reference* ptr) + : Value(ptr) + { concrete_type(PARENT); } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + 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); + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + +} diff --git a/src/libsass/src/ast_values.hpp b/src/libsass/src/ast_values.hpp new file mode 100644 index 000000000..b9f9d087e --- /dev/null +++ b/src/libsass/src/ast_values.hpp @@ -0,0 +1,498 @@ +#ifndef SASS_AST_VALUES_H +#define SASS_AST_VALUES_H + +// sass.hpp must go before all system headers to get the +// __EXTENSIONS__ fix on Solaris. +#include "sass.hpp" +#include "ast.hpp" + +namespace Sass { + + ////////////////////////////////////////////////////////////////////// + // Still just an expression, but with a to_string method + ////////////////////////////////////////////////////////////////////// + class PreValue : public Expression { + public: + PreValue(ParserState pstate, bool d = false, bool e = false, bool i = false, Type ct = NONE); + ATTACH_VIRTUAL_AST_OPERATIONS(PreValue); + virtual ~PreValue() { } + }; + + ////////////////////////////////////////////////////////////////////// + // base class for values that support operations + ////////////////////////////////////////////////////////////////////// + class Value : public PreValue { + public: + Value(ParserState 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; + + // We can give some reasonable implementations by using + // inverst operators on the specialized implementations + virtual bool operator> (const Expression& rhs) const { + return rhs < *this; + } + virtual bool operator!= (const Expression& rhs) const { + return !(*this == rhs); + } + + ATTACH_VIRTUAL_AST_OPERATIONS(Value); + + }; + + /////////////////////////////////////////////////////////////////////// + // 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(Expression_Obj e) override { is_expanded(false); } + private: + ADD_PROPERTY(enum Sass_Separator, separator) + ADD_PROPERTY(bool, is_arglist) + ADD_PROPERTY(bool, is_bracketed) + ADD_PROPERTY(bool, from_selector) + public: + List(ParserState pstate, size_t size = 0, enum Sass_Separator sep = SASS_SPACE, bool argl = false, bool bracket = false); + std::string type() const override { return is_arglist_ ? "arglist" : "list"; } + static std::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(); } + Expression_Obj value_at_index(size_t i); + + virtual size_t hash() const override; + virtual size_t size() const; + virtual void set_delayed(bool delayed) override; + + virtual bool operator< (const Expression& rhs) const override; + virtual bool operator== (const Expression& rhs) const override; + + ATTACH_AST_OPERATIONS(List) + ATTACH_CRTP_PERFORM_METHODS() + }; + + /////////////////////////////////////////////////////////////////////// + // Key value paris. + /////////////////////////////////////////////////////////////////////// + class Map : public Value, public Hashed { + void adjust_after_pushing(std::pair p) override { is_expanded(false); } + public: + Map(ParserState pstate, size_t size = 0); + std::string type() const override { return "map"; } + static std::string type_name() { return "map"; } + bool is_invisible() const override { return empty(); } + List_Obj to_list(ParserState& pstate); + + virtual size_t hash() const override; + + virtual bool operator< (const Expression& rhs) const override; + virtual bool operator== (const Expression& rhs) const override; + + ATTACH_AST_OPERATIONS(Map) + ATTACH_CRTP_PERFORM_METHODS() + }; + + + ////////////////////////////////////////////////////////////////////////// + // Binary expressions. Represents logical, relational, and arithmetic + // operations. Templatized to avoid large switch statements and repetitive + // subclassing. + ////////////////////////////////////////////////////////////////////////// + class Binary_Expression : public PreValue { + private: + HASH_PROPERTY(Operand, op) + HASH_PROPERTY(Expression_Obj, left) + HASH_PROPERTY(Expression_Obj, right) + mutable size_t hash_; + public: + Binary_Expression(ParserState pstate, + Operand op, Expression_Obj lhs, Expression_Obj rhs); + + const std::string type_name(); + const std::string separator(); + bool is_left_interpolant(void) const override; + bool is_right_interpolant(void) const override; + bool has_interpolant() const override; + + virtual void set_delayed(bool delayed) override; + + virtual bool operator< (const Expression& rhs) const override; + virtual bool operator==(const Expression& rhs) const override; + + virtual size_t hash() const override; + enum Sass_OP optype() const { return op_.operand; } + ATTACH_AST_OPERATIONS(Binary_Expression) + ATTACH_CRTP_PERFORM_METHODS() + }; + + //////////////////////////////////////////////////// + // Function reference. + //////////////////////////////////////////////////// + class Function final : public Value { + public: + ADD_PROPERTY(Definition_Obj, definition) + ADD_PROPERTY(bool, is_css) + public: + Function(ParserState pstate, Definition_Obj def, bool css); + + std::string type() const override { return "function"; } + static std::string type_name() { return "function"; } + bool is_invisible() const override { return true; } + + std::string name(); + + bool operator< (const Expression& rhs) const override; + bool operator== (const Expression& rhs) const override; + + ATTACH_AST_OPERATIONS(Function) + ATTACH_CRTP_PERFORM_METHODS() + }; + + ////////////////// + // 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_; + public: + Function_Call(ParserState pstate, std::string n, Arguments_Obj args, void* cookie); + Function_Call(ParserState pstate, std::string n, Arguments_Obj args, Function_Obj func); + Function_Call(ParserState pstate, std::string n, Arguments_Obj args); + + Function_Call(ParserState pstate, String_Obj n, Arguments_Obj args, void* cookie); + Function_Call(ParserState pstate, String_Obj n, Arguments_Obj args, Function_Obj func); + Function_Call(ParserState pstate, String_Obj n, Arguments_Obj args); + + std::string name() const; + bool is_css(); + + bool operator==(const Expression& rhs) const override; + + size_t hash() const override; + + ATTACH_AST_OPERATIONS(Function_Call) + ATTACH_CRTP_PERFORM_METHODS() + }; + + /////////////////////// + // Variable references. + /////////////////////// + class Variable final : public PreValue { + ADD_CONSTREF(std::string, name) + public: + Variable(ParserState pstate, std::string n); + virtual bool operator==(const Expression& rhs) const override; + virtual size_t hash() const override; + ATTACH_AST_OPERATIONS(Variable) + ATTACH_CRTP_PERFORM_METHODS() + }; + + //////////////////////////////////////////////// + // Numbers, percentages, dimensions, and colors. + //////////////////////////////////////////////// + class Number final : public Value, public Units { + HASH_PROPERTY(double, value) + ADD_PROPERTY(bool, zero) + mutable size_t hash_; + public: + Number(ParserState pstate, double val, std::string u = "", bool zero = true); + + bool zero() { return zero_; } + + std::string type() const override { return "number"; } + static std::string type_name() { return "number"; } + + // cancel out unnecessary units + // result will be in input units + void reduce(); + + // normalize units to defaults + // needed to compare two numbers + void normalize(); + + size_t hash() const override; + + bool operator< (const Number& rhs) const; + bool operator== (const Number& rhs) const; + bool operator< (const Expression& rhs) const override; + bool operator== (const Expression& rhs) const override; + ATTACH_AST_OPERATIONS(Number) + ATTACH_CRTP_PERFORM_METHODS() + }; + + ////////// + // Colors. + ////////// + class Color : public Value { + ADD_CONSTREF(std::string, disp) + HASH_PROPERTY(double, a) + protected: + mutable size_t hash_; + public: + Color(ParserState pstate, double a = 1, const std::string disp = ""); + + std::string type() const override { return "color"; } + static std::string type_name() { return "color"; } + + virtual size_t hash() const override = 0; + + bool operator< (const Expression& rhs) const override; + bool operator== (const Expression& rhs) const override; + + virtual Color_RGBA* copyAsRGBA() const = 0; + virtual Color_RGBA* toRGBA() = 0; + + virtual Color_HSLA* copyAsHSLA() const = 0; + virtual Color_HSLA* toHSLA() = 0; + + ATTACH_VIRTUAL_AST_OPERATIONS(Color) + }; + + ////////// + // Colors. + ////////// + class Color_RGBA final : public Color { + HASH_PROPERTY(double, r) + HASH_PROPERTY(double, g) + HASH_PROPERTY(double, b) + public: + Color_RGBA(ParserState pstate, double r, double g, double b, double a = 1, const std::string disp = ""); + + std::string type() const override { return "color"; } + static std::string type_name() { return "color"; } + + size_t hash() const override; + + Color_RGBA* copyAsRGBA() const override; + Color_RGBA* toRGBA() override { return this; } + + Color_HSLA* copyAsHSLA() const override; + Color_HSLA* toHSLA() override { return copyAsHSLA(); } + + bool operator< (const Expression& rhs) const override; + bool operator== (const Expression& rhs) const override; + + ATTACH_AST_OPERATIONS(Color_RGBA) + ATTACH_CRTP_PERFORM_METHODS() + }; + + + ////////// + // Colors. + ////////// + class Color_HSLA final : public Color { + HASH_PROPERTY(double, h) + HASH_PROPERTY(double, s) + HASH_PROPERTY(double, l) + public: + Color_HSLA(ParserState pstate, double h, double s, double l, double a = 1, const std::string disp = ""); + + std::string type() const override { return "color"; } + static std::string type_name() { return "color"; } + + size_t hash() const override; + + Color_RGBA* copyAsRGBA() const override; + Color_RGBA* toRGBA() override { return copyAsRGBA(); } + + Color_HSLA* copyAsHSLA() const override; + Color_HSLA* toHSLA() override { return this; } + + bool operator< (const Expression& rhs) const override; + bool operator== (const Expression& rhs) const override; + + ATTACH_AST_OPERATIONS(Color_HSLA) + ATTACH_CRTP_PERFORM_METHODS() + }; + + ////////////////////////////// + // Errors from Sass_Values. + ////////////////////////////// + class Custom_Error final : public Value { + ADD_CONSTREF(std::string, message) + public: + Custom_Error(ParserState pstate, std::string msg); + bool operator< (const Expression& rhs) const override; + bool operator== (const Expression& rhs) const override; + ATTACH_AST_OPERATIONS(Custom_Error) + ATTACH_CRTP_PERFORM_METHODS() + }; + + ////////////////////////////// + // Warnings from Sass_Values. + ////////////////////////////// + class Custom_Warning final : public Value { + ADD_CONSTREF(std::string, message) + public: + Custom_Warning(ParserState pstate, std::string msg); + bool operator< (const Expression& rhs) const override; + bool operator== (const Expression& rhs) const override; + ATTACH_AST_OPERATIONS(Custom_Warning) + ATTACH_CRTP_PERFORM_METHODS() + }; + + //////////// + // Booleans. + //////////// + class Boolean final : public Value { + HASH_PROPERTY(bool, value) + mutable size_t hash_; + public: + Boolean(ParserState pstate, bool val); + operator bool() override { return value_; } + + std::string type() const override { return "bool"; } + static std::string type_name() { return "bool"; } + + size_t hash() const override; + + bool is_false() override { return !value_; } + + bool operator< (const Expression& rhs) const override; + bool operator== (const Expression& rhs) const override; + + ATTACH_AST_OPERATIONS(Boolean) + ATTACH_CRTP_PERFORM_METHODS() + }; + + //////////////////////////////////////////////////////////////////////// + // Abstract base class for Sass string values. Includes interpolated and + // "flat" strings. + //////////////////////////////////////////////////////////////////////// + class String : public Value { + public: + String(ParserState pstate, bool delayed = false); + static std::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() + }; + 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_; + public: + String_Schema(ParserState pstate, size_t size = 0, bool css = true); + + std::string type() const override { return "string"; } + static std::string type_name() { return "string"; } + + bool is_left_interpolant(void) const override; + bool is_right_interpolant(void) const override; + + bool has_interpolants(); + void rtrim() override; + size_t hash() const override; + virtual void set_delayed(bool delayed) override; + + bool operator< (const Expression& rhs) const override; + bool operator==(const Expression& rhs) const override; + ATTACH_AST_OPERATIONS(String_Schema) + ATTACH_CRTP_PERFORM_METHODS() + }; + + //////////////////////////////////////////////////////// + // Flat strings -- the lowest level of raw textual data. + //////////////////////////////////////////////////////// + class String_Constant : public String { + ADD_PROPERTY(char, quote_mark) + HASH_CONSTREF(std::string, value) + protected: + mutable size_t hash_; + public: + String_Constant(ParserState pstate, std::string val, bool css = true); + String_Constant(ParserState pstate, const char* beg, bool css = true); + String_Constant(ParserState pstate, const char* beg, const char* end, bool css = true); + String_Constant(ParserState pstate, const Token& tok, bool css = true); + std::string type() const override { return "string"; } + static std::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 std::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(ParserState pstate, std::string val, char q = 0, + bool keep_utf8_escapes = false, bool skip_unquoting = false, + bool strict_unquoting = true, bool css = true); + bool operator< (const Expression& rhs) const override; + bool operator==(const Expression& rhs) const override; + // quotes are forced on inspection + std::string inspect() const override; + ATTACH_AST_OPERATIONS(String_Quoted) + ATTACH_CRTP_PERFORM_METHODS() + }; + + ////////////////// + // The null value. + ////////////////// + class Null final : public Value { + public: + Null(ParserState pstate); + std::string type() const override { return "null"; } + static std::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; + + bool operator< (const Expression& rhs) const override; + bool operator== (const Expression& rhs) const override; + + ATTACH_AST_OPERATIONS(Null) + ATTACH_CRTP_PERFORM_METHODS() + }; + + ////////////////////////////////// + // The Parent Reference Expression. + ////////////////////////////////// + class Parent_Reference final : public Value { + public: + Parent_Reference(ParserState pstate); + std::string type() const override { return "parent"; } + static std::string type_name() { return "parent"; } + bool operator< (const Expression& rhs) const override { + return false; // they are always equal + } + bool operator==(const Expression& rhs) const override { + return true; // they are always equal + }; + ATTACH_AST_OPERATIONS(Parent_Reference) + ATTACH_CRTP_PERFORM_METHODS() + }; + +} + +#endif diff --git a/src/libsass/src/backtrace.cpp b/src/libsass/src/backtrace.cpp index 8da963a72..b9ee1dff1 100644 --- a/src/libsass/src/backtrace.cpp +++ b/src/libsass/src/backtrace.cpp @@ -24,6 +24,8 @@ namespace Sass { ss << indent; ss << "on line "; ss << trace.pstate.line + 1; + ss << ":"; + ss << trace.pstate.column + 1; ss << " of " << rel_path; // ss << trace.caller; first = false; @@ -33,6 +35,8 @@ namespace Sass { ss << indent; ss << "from line "; ss << trace.pstate.line + 1; + ss << ":"; + ss << trace.pstate.column + 1; ss << " of " << rel_path; } diff --git a/src/libsass/src/base64vlq.cpp b/src/libsass/src/base64vlq.cpp index be2fb4926..d618dae5b 100644 --- a/src/libsass/src/base64vlq.cpp +++ b/src/libsass/src/base64vlq.cpp @@ -1,4 +1,7 @@ +// sass.hpp must go before all system headers to get the +// __EXTENSIONS__ fix on Solaris. #include "sass.hpp" + #include "base64vlq.hpp" namespace Sass { diff --git a/src/libsass/src/bind.cpp b/src/libsass/src/bind.cpp index ec20ac838..0159c2a12 100644 --- a/src/libsass/src/bind.cpp +++ b/src/libsass/src/bind.cpp @@ -1,6 +1,7 @@ #include "sass.hpp" #include "bind.hpp" #include "ast.hpp" +#include "backtrace.hpp" #include "context.hpp" #include "expand.hpp" #include "eval.hpp" @@ -10,7 +11,7 @@ namespace Sass { - void bind(std::string type, std::string name, Parameters_Obj ps, Arguments_Obj as, Context* ctx, Env* env, Eval* eval) + void bind(std::string type, std::string name, Parameters_Obj ps, Arguments_Obj as, Env* env, Eval* eval, Backtraces& traces) { std::string callee(type + " " + name); @@ -54,7 +55,7 @@ namespace Sass { std::stringstream msg; msg << "wrong number of arguments (" << LA << " for " << LP << ")"; msg << " for `" << name << "'"; - return error(msg.str(), as->pstate(), eval->exp.traces); + return error(msg.str(), as->pstate(), traces); } Parameter_Obj p = ps->at(ip); @@ -66,7 +67,7 @@ namespace Sass { // 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_Ptr arglist = SASS_MEMORY_NEW(List, + List* arglist = SASS_MEMORY_NEW(List, p->pstate(), 0, rest->separator(), @@ -94,7 +95,7 @@ namespace Sass { } else if (a->is_keyword_argument()) { // expand keyword arguments into their parameters - List_Ptr arglist = SASS_MEMORY_NEW(List, p->pstate(), 0, SASS_COMMA, true); + 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()) { @@ -107,8 +108,8 @@ namespace Sass { false, false)); } else { - eval->exp.traces.push_back(Backtrace(key->pstate())); - throw Exception::InvalidVarKwdType(key->pstate(), eval->exp.traces, key->inspect(), a); + traces.push_back(Backtrace(key->pstate())); + throw Exception::InvalidVarKwdType(key->pstate(), traces, key->inspect(), a); } } @@ -167,7 +168,7 @@ namespace Sass { } // consumed parameter ++ip; - // no more paramaters + // no more parameters break; } @@ -203,7 +204,7 @@ namespace Sass { // otherwise move one of the rest args into the param, converting to argument if necessary Expression_Obj obj = arglist->at(0); if (!(a = Cast(obj))) { - Expression_Ptr a_to_convert = obj; + Expression* a_to_convert = obj; a = SASS_MEMORY_NEW(Argument, a_to_convert->pstate(), a_to_convert, @@ -220,17 +221,17 @@ namespace Sass { Map_Obj argmap = Cast(a->value()); for (auto key : argmap->keys()) { - String_Constant_Ptr val = Cast(key); + String_Constant* val = Cast(key); if (val == NULL) { - eval->exp.traces.push_back(Backtrace(key->pstate())); - throw Exception::InvalidVarKwdType(key->pstate(), eval->exp.traces, key->inspect(), a); + traces.push_back(Backtrace(key->pstate())); + throw Exception::InvalidVarKwdType(key->pstate(), traces, key->inspect(), a); } std::string param = "$" + unquote(val->value()); if (!param_map.count(param)) { std::stringstream msg; msg << callee << " has no parameter named " << param; - error(msg.str(), a->pstate(), eval->exp.traces); + error(msg.str(), a->pstate(), traces); } env->local_frame()[param] = argmap->at(key); } @@ -245,7 +246,7 @@ namespace Sass { std::stringstream msg; msg << "parameter " << p->name() << " provided more than once in call to " << callee; - error(msg.str(), a->pstate(), eval->exp.traces); + error(msg.str(), a->pstate(), traces); } // ordinal arg -- bind it to the next param env->local_frame()[p->name()] = a->value(); @@ -259,7 +260,7 @@ namespace Sass { } else { std::stringstream msg; msg << callee << " has no parameter named " << a->name(); - error(msg.str(), a->pstate(), eval->exp.traces); + error(msg.str(), a->pstate(), traces); } } if (param_map[a->name()]) { @@ -267,14 +268,14 @@ namespace Sass { std::stringstream msg; msg << "argument " << a->name() << " of " << callee << "cannot be used as named argument"; - error(msg.str(), a->pstate(), eval->exp.traces); + error(msg.str(), a->pstate(), traces); } } if (env->has_local(a->name())) { std::stringstream msg; msg << "parameter " << p->name() << "provided more than once in call to " << callee; - error(msg.str(), a->pstate(), eval->exp.traces); + error(msg.str(), a->pstate(), traces); } env->local_frame()[a->name()] = a->value(); } @@ -294,12 +295,12 @@ namespace Sass { env->local_frame()[leftover->name()] = varargs; } else if (leftover->default_value()) { - Expression_Ptr dv = leftover->default_value()->perform(eval); + 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(), eval->exp.traces, name, leftover->name(), type); + throw Exception::MissingArgument(as->pstate(), traces, name, leftover->name(), type); } } } diff --git a/src/libsass/src/bind.hpp b/src/libsass/src/bind.hpp index 93a503aa6..57bcd01f9 100644 --- a/src/libsass/src/bind.hpp +++ b/src/libsass/src/bind.hpp @@ -2,12 +2,14 @@ #define SASS_BIND_H #include +#include "backtrace.hpp" #include "environment.hpp" #include "ast_fwd_decl.hpp" namespace Sass { - void bind(std::string type, std::string name, Parameters_Obj, Arguments_Obj, Context*, Env*, Eval*); + void bind(std::string type, std::string name, Parameters_Obj, Arguments_Obj, Env*, Eval*, Backtraces& traces); + } #endif diff --git a/src/libsass/src/c2ast.cpp b/src/libsass/src/c2ast.cpp new file mode 100644 index 000000000..384e2dec5 --- /dev/null +++ b/src/libsass/src/c2ast.cpp @@ -0,0 +1,64 @@ +#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, ParserState 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: " + std::string(sass_error_get_message(v)), pstate, traces); + } break; + case SASS_WARNING: { + error("Warning in C function: " + std::string(sass_warning_get_message(v)), pstate, traces); + } break; + default: break; + } + return e; + } + +} diff --git a/src/libsass/src/c2ast.hpp b/src/libsass/src/c2ast.hpp new file mode 100644 index 000000000..7f6470969 --- /dev/null +++ b/src/libsass/src/c2ast.hpp @@ -0,0 +1,14 @@ +#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, ParserState pstate); + +} + +#endif diff --git a/src/libsass/src/cencode.c b/src/libsass/src/cencode.c index 9109f4b22..3932fcc3e 100644 --- a/src/libsass/src/cencode.c +++ b/src/libsass/src/cencode.c @@ -46,9 +46,8 @@ int base64_encode_block(const char* plaintext_in, int length_in, char* code_out, result = (fragment & 0x0fc) >> 2; *codechar++ = base64_encode_value(result); result = (fragment & 0x003) << 4; - #ifndef _MSC_VER - /* fall through */ - #endif + /* fall through */ + case step_B: if (plainchar == plaintextend) { @@ -60,9 +59,8 @@ int base64_encode_block(const char* plaintext_in, int length_in, char* code_out, result |= (fragment & 0x0f0) >> 4; *codechar++ = base64_encode_value(result); result = (fragment & 0x00f) << 2; - #ifndef _MSC_VER - /* fall through */ - #endif + /* fall through */ + case step_C: if (plainchar == plaintextend) { diff --git a/src/libsass/src/check_nesting.cpp b/src/libsass/src/check_nesting.cpp index 880bcca37..649b8ae1f 100644 --- a/src/libsass/src/check_nesting.cpp +++ b/src/libsass/src/check_nesting.cpp @@ -1,31 +1,32 @@ +// sass.hpp must go before all system headers to get the +// __EXTENSIONS__ fix on Solaris. #include "sass.hpp" -#include - +#include "ast.hpp" #include "check_nesting.hpp" namespace Sass { CheckNesting::CheckNesting() - : parents(std::vector()), + : parents(std::vector()), traces(std::vector()), parent(0), current_mixin_definition(0) { } - void error(AST_Node_Ptr node, Backtraces traces, std::string msg) { + void error(AST_Node* node, Backtraces traces, std::string msg) { traces.push_back(Backtrace(node->pstate())); throw Exception::InvalidSass(node->pstate(), traces, msg); } - Statement_Ptr CheckNesting::visit_children(Statement_Ptr parent) + Statement* CheckNesting::visit_children(Statement* parent) { - Statement_Ptr old_parent = this->parent; + Statement* old_parent = this->parent; - if (At_Root_Block_Ptr root = Cast(parent)) { - std::vector old_parents = this->parents; - std::vector new_parents; + if (At_Root_Block* root = Cast(parent)) { + std::vector old_parents = this->parents; + std::vector new_parents; for (size_t i = 0, L = this->parents.size(); i < L; i++) { - Statement_Ptr p = this->parents.at(i); + Statement* p = this->parents.at(i); if (!root->exclude_node(p)) { new_parents.push_back(p); } @@ -33,8 +34,8 @@ namespace Sass { this->parents = new_parents; for (size_t i = this->parents.size(); i > 0; i--) { - Statement_Ptr p = 0; - Statement_Ptr gp = 0; + Statement* p = 0; + Statement* gp = 0; if (i > 0) p = this->parents.at(i - 1); if (i > 1) gp = this->parents.at(i - 2); @@ -44,8 +45,8 @@ namespace Sass { } } - At_Root_Block_Ptr ar = Cast(parent); - Block_Ptr ret = ar->block(); + At_Root_Block* ar = Cast(parent); + Block* ret = ar->block(); if (ret != NULL) { for (auto n : ret->elements()) { @@ -65,16 +66,16 @@ namespace Sass { this->parents.push_back(parent); - Block_Ptr b = Cast(parent); + Block* b = Cast(parent); - if (Trace_Ptr trace = Cast(parent)) { + if (Trace* trace = Cast(parent)) { if (trace->type() == 'i') { this->traces.push_back(Backtrace(trace->pstate())); } } if (!b) { - if (Has_Block_Ptr bb = Cast(parent)) { + if (Has_Block* bb = Cast(parent)) { b = bb->block(); } } @@ -88,7 +89,7 @@ namespace Sass { this->parent = old_parent; this->parents.pop_back(); - if (Trace_Ptr trace = Cast(parent)) { + if (Trace* trace = Cast(parent)) { if (trace->type() == 'i') { this->traces.pop_back(); } @@ -98,12 +99,12 @@ namespace Sass { } - Statement_Ptr CheckNesting::operator()(Block_Ptr b) + Statement* CheckNesting::operator()(Block* b) { return this->visit_children(b); } - Statement_Ptr CheckNesting::operator()(Definition_Ptr n) + Statement* CheckNesting::operator()(Definition* n) { if (!this->should_visit(n)) return NULL; if (!is_mixin(n)) { @@ -111,7 +112,7 @@ namespace Sass { return n; } - Definition_Ptr old_mixin_definition = this->current_mixin_definition; + Definition* old_mixin_definition = this->current_mixin_definition; this->current_mixin_definition = n; visit_children(n); @@ -121,25 +122,18 @@ namespace Sass { return n; } - Statement_Ptr CheckNesting::operator()(If_Ptr i) + Statement* CheckNesting::operator()(If* i) { this->visit_children(i); - if (Block_Ptr b = Cast(i->alternative())) { + if (Block* b = Cast(i->alternative())) { for (auto n : b->elements()) n->perform(this); } return i; } - Statement_Ptr CheckNesting::fallback_impl(Statement_Ptr s) - { - Block_Ptr b1 = Cast(s); - Has_Block_Ptr b2 = Cast(s); - return b1 || b2 ? visit_children(s) : s; - } - - bool CheckNesting::should_visit(Statement_Ptr node) + bool CheckNesting::should_visit(Statement* node) { if (!this->parent) return true; @@ -149,7 +143,7 @@ namespace Sass { if (is_charset(node)) { this->invalid_charset_parent(this->parent, node); } - if (Cast(node)) + if (Cast(node)) { this->invalid_extend_parent(this->parent, node); } // if (Cast(node)) @@ -164,7 +158,7 @@ namespace Sass { if (this->is_function(this->parent)) { this->invalid_function_child(node); } - if (Declaration_Ptr d = Cast(node)) + if (Declaration* d = Cast(node)) { this->invalid_prop_parent(this->parent, node); this->invalid_value_child(d->value()); @@ -179,14 +173,14 @@ namespace Sass { return true; } - void CheckNesting::invalid_content_parent(Statement_Ptr parent, AST_Node_Ptr node) + 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_Ptr parent, AST_Node_Ptr node) + void CheckNesting::invalid_charset_parent(Statement* parent, AST_Node* node) { if (!( is_root_node(parent) @@ -195,7 +189,7 @@ namespace Sass { } } - void CheckNesting::invalid_extend_parent(Statement_Ptr parent, AST_Node_Ptr node) + void CheckNesting::invalid_extend_parent(Statement* parent, AST_Node* node) { if (!( Cast(parent) || @@ -206,7 +200,7 @@ namespace Sass { } } - // void CheckNesting::invalid_import_parent(Statement_Ptr parent, AST_Node_Ptr node) + // void CheckNesting::invalid_import_parent(Statement* parent, AST_Node* node) // { // for (auto pp : this->parents) { // if ( @@ -231,9 +225,9 @@ namespace Sass { // } // } - void CheckNesting::invalid_mixin_definition_parent(Statement_Ptr parent, AST_Node_Ptr node) + void CheckNesting::invalid_mixin_definition_parent(Statement* parent, AST_Node* node) { - for (Statement_Ptr pp : this->parents) { + for (Statement* pp : this->parents) { if ( Cast(pp) || Cast(pp) || @@ -248,9 +242,9 @@ namespace Sass { } } - void CheckNesting::invalid_function_parent(Statement_Ptr parent, AST_Node_Ptr node) + void CheckNesting::invalid_function_parent(Statement* parent, AST_Node* node) { - for (Statement_Ptr pp : this->parents) { + for (Statement* pp : this->parents) { if ( Cast(pp) || Cast(pp) || @@ -265,7 +259,7 @@ namespace Sass { } } - void CheckNesting::invalid_function_child(Statement_Ptr child) + void CheckNesting::invalid_function_child(Statement* child) { if (!( Cast(child) || @@ -286,7 +280,7 @@ namespace Sass { } } - void CheckNesting::invalid_prop_child(Statement_Ptr child) + void CheckNesting::invalid_prop_child(Statement* child) { if (!( Cast(child) || @@ -302,7 +296,7 @@ namespace Sass { } } - void CheckNesting::invalid_prop_parent(Statement_Ptr parent, AST_Node_Ptr node) + void CheckNesting::invalid_prop_parent(Statement* parent, AST_Node* node) { if (!( is_mixin(parent) || @@ -316,13 +310,13 @@ namespace Sass { } } - void CheckNesting::invalid_value_child(AST_Node_Ptr d) + void CheckNesting::invalid_value_child(AST_Node* d) { - if (Map_Ptr m = Cast(d)) { + if (Map* m = Cast(d)) { traces.push_back(Backtrace(m->pstate())); throw Exception::InvalidValue(traces, *m); } - if (Number_Ptr n = Cast(d)) { + if (Number* n = Cast(d)) { if (!n->is_valid_css_unit()) { traces.push_back(Backtrace(n->pstate())); throw Exception::InvalidValue(traces, *n); @@ -333,14 +327,14 @@ namespace Sass { } - void CheckNesting::invalid_return_parent(Statement_Ptr parent, AST_Node_Ptr node) + 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_Ptr parent, Statement_Ptr grandparent) + bool CheckNesting::is_transparent_parent(Statement* parent, Statement* grandparent) { bool parent_bubbles = parent && parent->bubbles(); @@ -357,42 +351,43 @@ namespace Sass { valid_bubble_node; } - bool CheckNesting::is_charset(Statement_Ptr n) + bool CheckNesting::is_charset(Statement* n) { - Directive_Ptr d = Cast(n); + Directive* d = Cast(n); return d && d->keyword() == "charset"; } - bool CheckNesting::is_mixin(Statement_Ptr n) + bool CheckNesting::is_mixin(Statement* n) { - Definition_Ptr def = Cast(n); + Definition* def = Cast(n); return def && def->type() == Definition::MIXIN; } - bool CheckNesting::is_function(Statement_Ptr n) + bool CheckNesting::is_function(Statement* n) { - Definition_Ptr def = Cast(n); + Definition* def = Cast(n); return def && def->type() == Definition::FUNCTION; } - bool CheckNesting::is_root_node(Statement_Ptr n) + bool CheckNesting::is_root_node(Statement* n) { if (Cast(n)) return false; - Block_Ptr b = Cast(n); + Block* b = Cast(n); return b && b->is_root(); } - bool CheckNesting::is_at_root_node(Statement_Ptr n) + bool CheckNesting::is_at_root_node(Statement* n) { return Cast(n) != NULL; } - bool CheckNesting::is_directive_node(Statement_Ptr n) + bool CheckNesting::is_directive_node(Statement* n) { return Cast(n) || Cast(n) || - Cast(n) || - Cast(n); + Cast(n) || + Cast(n) || + Cast(n); } } diff --git a/src/libsass/src/check_nesting.hpp b/src/libsass/src/check_nesting.hpp index 62c38d9dc..6a04bfe97 100644 --- a/src/libsass/src/check_nesting.hpp +++ b/src/libsass/src/check_nesting.hpp @@ -1,63 +1,68 @@ #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 { + class CheckNesting : public Operation_CRTP { - std::vector parents; + std::vector parents; Backtraces traces; - Statement_Ptr parent; - Definition_Ptr current_mixin_definition; + Statement* parent; + Definition* current_mixin_definition; - Statement_Ptr fallback_impl(Statement_Ptr); - Statement_Ptr before(Statement_Ptr); - Statement_Ptr visit_children(Statement_Ptr); + Statement* before(Statement*); + Statement* visit_children(Statement*); public: CheckNesting(); ~CheckNesting() { } - Statement_Ptr operator()(Block_Ptr); - Statement_Ptr operator()(Definition_Ptr); - Statement_Ptr operator()(If_Ptr); + Statement* operator()(Block*); + Statement* operator()(Definition*); + Statement* operator()(If*); template - Statement_Ptr fallback(U x) { - Statement_Ptr n = Cast(x); - if (this->should_visit(n)) { - return fallback_impl(n); + Statement* fallback(U x) { + Statement* s = Cast(x); + if (s && this->should_visit(s)) { + Block* b1 = Cast(s); + Has_Block* b2 = Cast(s); + if (b1 || b2) return visit_children(s); } - return NULL; + return s; } private: - void invalid_content_parent(Statement_Ptr, AST_Node_Ptr); - void invalid_charset_parent(Statement_Ptr, AST_Node_Ptr); - void invalid_extend_parent(Statement_Ptr, AST_Node_Ptr); - // void invalid_import_parent(Statement_Ptr); - void invalid_mixin_definition_parent(Statement_Ptr, AST_Node_Ptr); - void invalid_function_parent(Statement_Ptr, AST_Node_Ptr); + 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_Ptr); - void invalid_prop_child(Statement_Ptr); - void invalid_prop_parent(Statement_Ptr, AST_Node_Ptr); - void invalid_return_parent(Statement_Ptr, AST_Node_Ptr); - void invalid_value_child(AST_Node_Ptr); + 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_Ptr, Statement_Ptr); + bool is_transparent_parent(Statement*, Statement*); - bool should_visit(Statement_Ptr); + bool should_visit(Statement*); - bool is_charset(Statement_Ptr); - bool is_mixin(Statement_Ptr); - bool is_function(Statement_Ptr); - bool is_root_node(Statement_Ptr); - bool is_at_root_node(Statement_Ptr); - bool is_directive_node(Statement_Ptr); + 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*); }; } diff --git a/src/libsass/src/color_maps.cpp b/src/libsass/src/color_maps.cpp index 129e47c5a..be38fcb08 100644 --- a/src/libsass/src/color_maps.cpp +++ b/src/libsass/src/color_maps.cpp @@ -1,6 +1,10 @@ +// 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" namespace Sass { @@ -159,158 +163,158 @@ namespace Sass { namespace Colors { const ParserState color_table("[COLOR TABLE]"); - const Color aliceblue(color_table, 240, 248, 255, 1); - const Color antiquewhite(color_table, 250, 235, 215, 1); - const Color cyan(color_table, 0, 255, 255, 1); - const Color aqua(color_table, 0, 255, 255, 1); - const Color aquamarine(color_table, 127, 255, 212, 1); - const Color azure(color_table, 240, 255, 255, 1); - const Color beige(color_table, 245, 245, 220, 1); - const Color bisque(color_table, 255, 228, 196, 1); - const Color black(color_table, 0, 0, 0, 1); - const Color blanchedalmond(color_table, 255, 235, 205, 1); - const Color blue(color_table, 0, 0, 255, 1); - const Color blueviolet(color_table, 138, 43, 226, 1); - const Color brown(color_table, 165, 42, 42, 1); - const Color burlywood(color_table, 222, 184, 135, 1); - const Color cadetblue(color_table, 95, 158, 160, 1); - const Color chartreuse(color_table, 127, 255, 0, 1); - const Color chocolate(color_table, 210, 105, 30, 1); - const Color coral(color_table, 255, 127, 80, 1); - const Color cornflowerblue(color_table, 100, 149, 237, 1); - const Color cornsilk(color_table, 255, 248, 220, 1); - const Color crimson(color_table, 220, 20, 60, 1); - const Color darkblue(color_table, 0, 0, 139, 1); - const Color darkcyan(color_table, 0, 139, 139, 1); - const Color darkgoldenrod(color_table, 184, 134, 11, 1); - const Color darkgray(color_table, 169, 169, 169, 1); - const Color darkgrey(color_table, 169, 169, 169, 1); - const Color darkgreen(color_table, 0, 100, 0, 1); - const Color darkkhaki(color_table, 189, 183, 107, 1); - const Color darkmagenta(color_table, 139, 0, 139, 1); - const Color darkolivegreen(color_table, 85, 107, 47, 1); - const Color darkorange(color_table, 255, 140, 0, 1); - const Color darkorchid(color_table, 153, 50, 204, 1); - const Color darkred(color_table, 139, 0, 0, 1); - const Color darksalmon(color_table, 233, 150, 122, 1); - const Color darkseagreen(color_table, 143, 188, 143, 1); - const Color darkslateblue(color_table, 72, 61, 139, 1); - const Color darkslategray(color_table, 47, 79, 79, 1); - const Color darkslategrey(color_table, 47, 79, 79, 1); - const Color darkturquoise(color_table, 0, 206, 209, 1); - const Color darkviolet(color_table, 148, 0, 211, 1); - const Color deeppink(color_table, 255, 20, 147, 1); - const Color deepskyblue(color_table, 0, 191, 255, 1); - const Color dimgray(color_table, 105, 105, 105, 1); - const Color dimgrey(color_table, 105, 105, 105, 1); - const Color dodgerblue(color_table, 30, 144, 255, 1); - const Color firebrick(color_table, 178, 34, 34, 1); - const Color floralwhite(color_table, 255, 250, 240, 1); - const Color forestgreen(color_table, 34, 139, 34, 1); - const Color magenta(color_table, 255, 0, 255, 1); - const Color fuchsia(color_table, 255, 0, 255, 1); - const Color gainsboro(color_table, 220, 220, 220, 1); - const Color ghostwhite(color_table, 248, 248, 255, 1); - const Color gold(color_table, 255, 215, 0, 1); - const Color goldenrod(color_table, 218, 165, 32, 1); - const Color gray(color_table, 128, 128, 128, 1); - const Color grey(color_table, 128, 128, 128, 1); - const Color green(color_table, 0, 128, 0, 1); - const Color greenyellow(color_table, 173, 255, 47, 1); - const Color honeydew(color_table, 240, 255, 240, 1); - const Color hotpink(color_table, 255, 105, 180, 1); - const Color indianred(color_table, 205, 92, 92, 1); - const Color indigo(color_table, 75, 0, 130, 1); - const Color ivory(color_table, 255, 255, 240, 1); - const Color khaki(color_table, 240, 230, 140, 1); - const Color lavender(color_table, 230, 230, 250, 1); - const Color lavenderblush(color_table, 255, 240, 245, 1); - const Color lawngreen(color_table, 124, 252, 0, 1); - const Color lemonchiffon(color_table, 255, 250, 205, 1); - const Color lightblue(color_table, 173, 216, 230, 1); - const Color lightcoral(color_table, 240, 128, 128, 1); - const Color lightcyan(color_table, 224, 255, 255, 1); - const Color lightgoldenrodyellow(color_table, 250, 250, 210, 1); - const Color lightgray(color_table, 211, 211, 211, 1); - const Color lightgrey(color_table, 211, 211, 211, 1); - const Color lightgreen(color_table, 144, 238, 144, 1); - const Color lightpink(color_table, 255, 182, 193, 1); - const Color lightsalmon(color_table, 255, 160, 122, 1); - const Color lightseagreen(color_table, 32, 178, 170, 1); - const Color lightskyblue(color_table, 135, 206, 250, 1); - const Color lightslategray(color_table, 119, 136, 153, 1); - const Color lightslategrey(color_table, 119, 136, 153, 1); - const Color lightsteelblue(color_table, 176, 196, 222, 1); - const Color lightyellow(color_table, 255, 255, 224, 1); - const Color lime(color_table, 0, 255, 0, 1); - const Color limegreen(color_table, 50, 205, 50, 1); - const Color linen(color_table, 250, 240, 230, 1); - const Color maroon(color_table, 128, 0, 0, 1); - const Color mediumaquamarine(color_table, 102, 205, 170, 1); - const Color mediumblue(color_table, 0, 0, 205, 1); - const Color mediumorchid(color_table, 186, 85, 211, 1); - const Color mediumpurple(color_table, 147, 112, 219, 1); - const Color mediumseagreen(color_table, 60, 179, 113, 1); - const Color mediumslateblue(color_table, 123, 104, 238, 1); - const Color mediumspringgreen(color_table, 0, 250, 154, 1); - const Color mediumturquoise(color_table, 72, 209, 204, 1); - const Color mediumvioletred(color_table, 199, 21, 133, 1); - const Color midnightblue(color_table, 25, 25, 112, 1); - const Color mintcream(color_table, 245, 255, 250, 1); - const Color mistyrose(color_table, 255, 228, 225, 1); - const Color moccasin(color_table, 255, 228, 181, 1); - const Color navajowhite(color_table, 255, 222, 173, 1); - const Color navy(color_table, 0, 0, 128, 1); - const Color oldlace(color_table, 253, 245, 230, 1); - const Color olive(color_table, 128, 128, 0, 1); - const Color olivedrab(color_table, 107, 142, 35, 1); - const Color orange(color_table, 255, 165, 0, 1); - const Color orangered(color_table, 255, 69, 0, 1); - const Color orchid(color_table, 218, 112, 214, 1); - const Color palegoldenrod(color_table, 238, 232, 170, 1); - const Color palegreen(color_table, 152, 251, 152, 1); - const Color paleturquoise(color_table, 175, 238, 238, 1); - const Color palevioletred(color_table, 219, 112, 147, 1); - const Color papayawhip(color_table, 255, 239, 213, 1); - const Color peachpuff(color_table, 255, 218, 185, 1); - const Color peru(color_table, 205, 133, 63, 1); - const Color pink(color_table, 255, 192, 203, 1); - const Color plum(color_table, 221, 160, 221, 1); - const Color powderblue(color_table, 176, 224, 230, 1); - const Color purple(color_table, 128, 0, 128, 1); - const Color red(color_table, 255, 0, 0, 1); - const Color rosybrown(color_table, 188, 143, 143, 1); - const Color royalblue(color_table, 65, 105, 225, 1); - const Color saddlebrown(color_table, 139, 69, 19, 1); - const Color salmon(color_table, 250, 128, 114, 1); - const Color sandybrown(color_table, 244, 164, 96, 1); - const Color seagreen(color_table, 46, 139, 87, 1); - const Color seashell(color_table, 255, 245, 238, 1); - const Color sienna(color_table, 160, 82, 45, 1); - const Color silver(color_table, 192, 192, 192, 1); - const Color skyblue(color_table, 135, 206, 235, 1); - const Color slateblue(color_table, 106, 90, 205, 1); - const Color slategray(color_table, 112, 128, 144, 1); - const Color slategrey(color_table, 112, 128, 144, 1); - const Color snow(color_table, 255, 250, 250, 1); - const Color springgreen(color_table, 0, 255, 127, 1); - const Color steelblue(color_table, 70, 130, 180, 1); - const Color tan(color_table, 210, 180, 140, 1); - const Color teal(color_table, 0, 128, 128, 1); - const Color thistle(color_table, 216, 191, 216, 1); - const Color tomato(color_table, 255, 99, 71, 1); - const Color turquoise(color_table, 64, 224, 208, 1); - const Color violet(color_table, 238, 130, 238, 1); - const Color wheat(color_table, 245, 222, 179, 1); - const Color white(color_table, 255, 255, 255, 1); - const Color whitesmoke(color_table, 245, 245, 245, 1); - const Color yellow(color_table, 255, 255, 0, 1); - const Color yellowgreen(color_table, 154, 205, 50, 1); - const Color rebeccapurple(color_table, 102, 51, 153, 1); - const Color transparent(color_table, 0, 0, 0, 0); + 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); } - const std::map colors_to_names { + 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 }, @@ -452,7 +456,7 @@ namespace Sass { { 102 * 0x10000 + 51 * 0x100 + 153, ColorNames::rebeccapurple } }; - const std::map names_to_colors + static const auto *const names_to_colors = new std::unordered_map { { ColorNames::aliceblue, &Colors::aliceblue }, { ColorNames::antiquewhite, &Colors::antiquewhite }, @@ -605,31 +609,31 @@ namespace Sass { { ColorNames::transparent, &Colors::transparent } }; - Color_Ptr_Const name_to_color(const char* key) + const Color_RGBA* name_to_color(const char* key) { return name_to_color(std::string(key)); } - Color_Ptr_Const name_to_color(const std::string& key) + const Color_RGBA* name_to_color(const std::string& key) { // case insensitive lookup. See #2462 - std::string lower{key}; - std::transform(lower.begin(), lower.end(), lower.begin(), ::tolower); + std::string lower = key; + Util::ascii_str_tolower(&lower); - auto p = names_to_colors.find(lower.c_str()); - if (p != names_to_colors.end()) { + auto p = names_to_colors->find(lower); + if (p != names_to_colors->end()) { return p->second; } - return 0; + return nullptr; } const char* color_to_name(const int key) { - auto p = colors_to_names.find(key); - if (p != colors_to_names.end()) { + auto p = colors_to_names->find(key); + if (p != colors_to_names->end()) { return p->second; } - return 0; + return nullptr; } const char* color_to_name(const double key) @@ -637,7 +641,7 @@ namespace Sass { return color_to_name((int)key); } - const char* color_to_name(const Color& c) + const char* color_to_name(const Color_RGBA& c) { double key = c.r() * 0x10000 + c.g() * 0x100 diff --git a/src/libsass/src/color_maps.hpp b/src/libsass/src/color_maps.hpp index d4fd41607..2bc4ba5d2 100644 --- a/src/libsass/src/color_maps.hpp +++ b/src/libsass/src/color_maps.hpp @@ -7,14 +7,6 @@ namespace Sass { - struct map_cmp_str - { - bool operator()(char const *a, char const *b) const - { - return std::strcmp(a, b) < 0; - } - }; - namespace ColorNames { extern const char aliceblue[]; @@ -169,161 +161,161 @@ namespace Sass { } namespace Colors { - extern const Color aliceblue; - extern const Color antiquewhite; - extern const Color cyan; - extern const Color aqua; - extern const Color aquamarine; - extern const Color azure; - extern const Color beige; - extern const Color bisque; - extern const Color black; - extern const Color blanchedalmond; - extern const Color blue; - extern const Color blueviolet; - extern const Color brown; - extern const Color burlywood; - extern const Color cadetblue; - extern const Color chartreuse; - extern const Color chocolate; - extern const Color coral; - extern const Color cornflowerblue; - extern const Color cornsilk; - extern const Color crimson; - extern const Color darkblue; - extern const Color darkcyan; - extern const Color darkgoldenrod; - extern const Color darkgray; - extern const Color darkgrey; - extern const Color darkgreen; - extern const Color darkkhaki; - extern const Color darkmagenta; - extern const Color darkolivegreen; - extern const Color darkorange; - extern const Color darkorchid; - extern const Color darkred; - extern const Color darksalmon; - extern const Color darkseagreen; - extern const Color darkslateblue; - extern const Color darkslategray; - extern const Color darkslategrey; - extern const Color darkturquoise; - extern const Color darkviolet; - extern const Color deeppink; - extern const Color deepskyblue; - extern const Color dimgray; - extern const Color dimgrey; - extern const Color dodgerblue; - extern const Color firebrick; - extern const Color floralwhite; - extern const Color forestgreen; - extern const Color magenta; - extern const Color fuchsia; - extern const Color gainsboro; - extern const Color ghostwhite; - extern const Color gold; - extern const Color goldenrod; - extern const Color gray; - extern const Color grey; - extern const Color green; - extern const Color greenyellow; - extern const Color honeydew; - extern const Color hotpink; - extern const Color indianred; - extern const Color indigo; - extern const Color ivory; - extern const Color khaki; - extern const Color lavender; - extern const Color lavenderblush; - extern const Color lawngreen; - extern const Color lemonchiffon; - extern const Color lightblue; - extern const Color lightcoral; - extern const Color lightcyan; - extern const Color lightgoldenrodyellow; - extern const Color lightgray; - extern const Color lightgrey; - extern const Color lightgreen; - extern const Color lightpink; - extern const Color lightsalmon; - extern const Color lightseagreen; - extern const Color lightskyblue; - extern const Color lightslategray; - extern const Color lightslategrey; - extern const Color lightsteelblue; - extern const Color lightyellow; - extern const Color lime; - extern const Color limegreen; - extern const Color linen; - extern const Color maroon; - extern const Color mediumaquamarine; - extern const Color mediumblue; - extern const Color mediumorchid; - extern const Color mediumpurple; - extern const Color mediumseagreen; - extern const Color mediumslateblue; - extern const Color mediumspringgreen; - extern const Color mediumturquoise; - extern const Color mediumvioletred; - extern const Color midnightblue; - extern const Color mintcream; - extern const Color mistyrose; - extern const Color moccasin; - extern const Color navajowhite; - extern const Color navy; - extern const Color oldlace; - extern const Color olive; - extern const Color olivedrab; - extern const Color orange; - extern const Color orangered; - extern const Color orchid; - extern const Color palegoldenrod; - extern const Color palegreen; - extern const Color paleturquoise; - extern const Color palevioletred; - extern const Color papayawhip; - extern const Color peachpuff; - extern const Color peru; - extern const Color pink; - extern const Color plum; - extern const Color powderblue; - extern const Color purple; - extern const Color red; - extern const Color rosybrown; - extern const Color royalblue; - extern const Color saddlebrown; - extern const Color salmon; - extern const Color sandybrown; - extern const Color seagreen; - extern const Color seashell; - extern const Color sienna; - extern const Color silver; - extern const Color skyblue; - extern const Color slateblue; - extern const Color slategray; - extern const Color slategrey; - extern const Color snow; - extern const Color springgreen; - extern const Color steelblue; - extern const Color tan; - extern const Color teal; - extern const Color thistle; - extern const Color tomato; - extern const Color turquoise; - extern const Color violet; - extern const Color wheat; - extern const Color white; - extern const Color whitesmoke; - extern const Color yellow; - extern const Color yellowgreen; - extern const Color rebeccapurple; - extern const Color transparent; + 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; } - Color_Ptr_Const name_to_color(const char*); - Color_Ptr_Const name_to_color(const std::string&); + const Color_RGBA* name_to_color(const char*); + const Color_RGBA* name_to_color(const std::string&); const char* color_to_name(const int); - const char* color_to_name(const Color&); + const char* color_to_name(const Color_RGBA&); const char* color_to_name(const double); } diff --git a/src/libsass/src/constants.cpp b/src/libsass/src/constants.cpp index 0ba28e20c..8d9b64a4a 100644 --- a/src/libsass/src/constants.cpp +++ b/src/libsass/src/constants.cpp @@ -1,4 +1,7 @@ +// sass.hpp must go before all system headers to get the +// __EXTENSIONS__ fix on Solaris. #include "sass.hpp" + #include "constants.hpp" namespace Sass { @@ -18,6 +21,15 @@ namespace Sass { 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"; @@ -33,6 +45,7 @@ namespace Sass { extern const char for_kwd[] = "@for"; extern const char from_kwd[] = "from"; extern const char to_kwd[] = "to"; + extern const char of_kwd[] = "of"; extern const char through_kwd[] = "through"; extern const char each_kwd[] = "@each"; extern const char in_kwd[] = "in"; @@ -116,6 +129,9 @@ namespace Sass { 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[] = ""; @@ -146,6 +162,10 @@ namespace Sass { extern const char uri_chars[] = ":;/?!%&#@|[]{}'`^\"*+-.,_=~"; extern const char real_uri_chars[] = "#%&"; + extern const char selector_combinator_child[] = ">"; + extern const char selector_combinator_general[] = "~"; + extern const char selector_combinator_adjacent[] = "+"; + // some specific constant character classes // they must be static to be useable by lexer extern const char static_ops[] = "*/%"; diff --git a/src/libsass/src/constants.hpp b/src/libsass/src/constants.hpp index 4fe93571e..81ada2749 100644 --- a/src/libsass/src/constants.hpp +++ b/src/libsass/src/constants.hpp @@ -18,6 +18,16 @@ namespace Sass { 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[]; @@ -33,6 +43,7 @@ namespace Sass { extern const char for_kwd[]; extern const char from_kwd[]; extern const char to_kwd[]; + extern const char of_kwd[]; extern const char through_kwd[]; extern const char each_kwd[]; extern const char in_kwd[]; @@ -117,6 +128,9 @@ namespace Sass { 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[]; @@ -148,6 +162,11 @@ namespace Sass { extern const char uri_chars[]; extern const char real_uri_chars[]; + // constants for selector combinators + extern const char selector_combinator_child[]; + extern const char selector_combinator_general[]; + extern const char selector_combinator_adjacent[]; + // some specific constant character classes // they must be static to be useable by lexer extern const char static_ops[]; diff --git a/src/libsass/src/context.cpp b/src/libsass/src/context.cpp index dae2cbd75..82c25b276 100644 --- a/src/libsass/src/context.cpp +++ b/src/libsass/src/context.cpp @@ -1,34 +1,22 @@ +// sass.hpp must go before all system headers to get the +// __EXTENSIONS__ fix on Solaris. #include "sass.hpp" -#include -#include -#include -#include -#include -#include - #include "ast.hpp" -#include "util.hpp" -#include "sass.h" + +#include "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 "plugins.hpp" -#include "constants.hpp" -#include "parser.hpp" -#include "file.hpp" -#include "inspect.hpp" -#include "output.hpp" #include "expand.hpp" -#include "eval.hpp" -#include "check_nesting.hpp" +#include "parser.hpp" #include "cssize.hpp" -#include "listize.hpp" -#include "extend.hpp" -#include "remove_placeholders.hpp" -#include "functions.hpp" -#include "sass_functions.hpp" -#include "backtrace.hpp" -#include "sass2scss.h" -#include "prelexer.hpp" -#include "emitter.hpp" namespace Sass { using namespace Constants; @@ -40,23 +28,17 @@ namespace Sass { static std::string safe_input(const char* in_path) { - // enforce some safe defaults - // used to create relative file links - std::string safe_path(in_path ? in_path : ""); - return safe_path == "" ? "stdin" : safe_path; + if (in_path == nullptr || in_path[0] == '\0') return "stdin"; + return in_path; } - static std::string safe_output(const char* out_path, const std::string& input_path = "") + static std::string safe_output(const char* out_path, std::string input_path) { - std::string safe_path(out_path ? out_path : ""); - // maybe we can extract an output path from input path - if (safe_path == "" && input_path != "") { - int lastindex = static_cast(input_path.find_last_of(".")); - return (lastindex > -1 ? input_path.substr(0, lastindex) : input_path) + ".css"; + 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"; } - // enforce some safe defaults - // used to create relative file links - return safe_path == "" ? "stdout" : safe_path; + return out_path; } Context::Context(struct Sass_Context& c_ctx) @@ -71,10 +53,10 @@ namespace Sass { strings(), resources(), sheets(), - subset_map(), import_stack(), callee_stack(), traces(), + extender(Extender::NORMAL, traces), c_compiler(NULL), c_headers (std::vector()), @@ -150,7 +132,7 @@ namespace Sass { } // clear inner structures (vectors) and input source resources.clear(); import_stack.clear(); - subset_map.clear(), sheets.clear(); + sheets.clear(); } Data_Context::~Data_Context() @@ -382,7 +364,7 @@ namespace Sass { } - void Context::import_url (Import_Ptr imp, std::string load_path, const std::string& ctx_path) { + void Context::import_url (Import* imp, std::string load_path, const std::string& ctx_path) { ParserState pstate(imp->pstate()); std::string imp_path(unquote(load_path)); @@ -395,17 +377,17 @@ namespace Sass { // if (protocol.compare("file") && true) { } } - // add urls (protocol other than file) and urls without procotol to `urls` member + // 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_Ptr loc = SASS_MEMORY_NEW(String_Constant, pstate, unquote(load_path)); + 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_Ptr new_url = SASS_MEMORY_NEW(Function_Call, pstate, "url", loc_args); + Function_Call* new_url = SASS_MEMORY_NEW(Function_Call, pstate, std::string("url"), loc_args); imp->urls().push_back(new_url); } else { @@ -421,7 +403,7 @@ namespace Sass { // call custom importers on the given (unquoted) load_path and eventually parse the resulting style_sheet - bool Context::call_loader(const std::string& load_path, const char* ctx_path, ParserState& pstate, Import_Ptr imp, std::vector importers, bool only_one) + bool Context::call_loader(const std::string& load_path, const char* ctx_path, ParserState& pstate, Import* imp, std::vector importers, bool only_one) { // unique counter size_t count = 0; @@ -519,7 +501,7 @@ namespace Sass { OutputBuffer emitted = emitter.get_buffer(); // should we append a source map url? if (!c_options.omit_source_map_url) { - // generate an embeded source map + // generate an embedded source map if (c_options.source_map_embed) { emitted.buffer += linefeed; emitted.buffer += format_embedded_source_map(); @@ -556,7 +538,7 @@ namespace Sass { { // check if entry file is given - if (input_path.empty()) return 0; + if (input_path.empty()) return {}; // create absolute path from input filename // ToDo: this should be resolved via custom importers @@ -602,7 +584,7 @@ namespace Sass { { // check if source string is given - if (!source_c_str) return 0; + if (!source_c_str) return {}; // convert indented sass syntax if(c_options.is_indented_syntax_src) { @@ -639,17 +621,15 @@ namespace Sass { return compile(); } - - // parse root block from includes Block_Obj Context::compile() { // abort if there is no data - if (resources.size() == 0) return 0; + 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 0; + if (root.isNull()) return {}; Env global; // create root environment // register built-in functions on env register_built_in_functions(*this, &global); @@ -668,23 +648,23 @@ namespace Sass { } // expand and eval the tree root = expand(root); + + Extension unsatisfied; + // check that all extends were used + if (extender.checkForUnsatisfiedExtends(unsatisfied)) { + throw Exception::UnsatisfiedExtend(traces, unsatisfied); + } + // check nesting check_nesting(root); // merge and bubble certain rules root = cssize(root); - // should we extend something? - if (!subset_map.empty()) { - // create crtp visitor object - Extend extend(subset_map); - extend.setEval(expand.eval); - // extend tree nodes - extend(root); - } // clean up by removing empty placeholders // ToDo: maybe we can do this somewhere else? Remove_Placeholders remove_placeholders; root->perform(&remove_placeholders); + // return processed tree return root; } @@ -732,14 +712,14 @@ namespace Sass { void register_function(Context& ctx, Signature sig, Native_Function f, Env* env) { - Definition_Ptr def = make_native_function(sig, f, ctx); + 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_Ptr def = make_native_function(sig, f, ctx); + Definition* def = make_native_function(sig, f, ctx); std::stringstream ss; ss << def->name() << "[f]" << arity; def->environment(env); @@ -748,11 +728,11 @@ namespace Sass { void register_overload_stub(Context& ctx, std::string name, Env* env) { - Definition_Ptr stub = SASS_MEMORY_NEW(Definition, + Definition* stub = SASS_MEMORY_NEW(Definition, ParserState("[built-in function]"), 0, name, - 0, + {}, 0, true); (*env)[name + "[f]"] = stub; @@ -872,7 +852,7 @@ namespace Sass { } void register_c_function(Context& ctx, Env* env, Sass_Function_Entry descr) { - Definition_Ptr def = make_c_function(descr, ctx); + Definition* def = make_c_function(descr, ctx); def->environment(env); (*env)[def->name() + "[f]"] = def; } diff --git a/src/libsass/src/context.hpp b/src/libsass/src/context.hpp index d3caba13e..1d25112e1 100644 --- a/src/libsass/src/context.hpp +++ b/src/libsass/src/context.hpp @@ -1,40 +1,32 @@ #ifndef SASS_CONTEXT_H #define SASS_CONTEXT_H -#include -#include -#include +// sass.hpp must go before all system headers to get the +// __EXTENSIONS__ fix on Solaris. +#include "sass.hpp" +#include "ast.hpp" + #define BUFFERSIZE 255 #include "b64/encode.h" -#include "ast_fwd_decl.hpp" -#include "kwd_arg_macros.hpp" -#include "ast_fwd_decl.hpp" #include "sass_context.hpp" -#include "environment.hpp" -#include "source_map.hpp" -#include "subset_map.hpp" -#include "backtrace.hpp" -#include "output.hpp" +#include "stylesheet.hpp" #include "plugins.hpp" -#include "file.hpp" - - -struct Sass_Function; +#include "output.hpp" namespace Sass { class Context { public: - void import_url (Import_Ptr imp, std::string load_path, const std::string& ctx_path); - bool call_headers(const std::string& load_path, const char* ctx_path, ParserState& pstate, Import_Ptr imp) + void import_url (Import* imp, std::string load_path, const std::string& ctx_path); + bool call_headers(const std::string& load_path, const char* ctx_path, ParserState& pstate, Import* imp) { return call_loader(load_path, ctx_path, pstate, imp, c_headers, false); }; - bool call_importers(const std::string& load_path, const char* ctx_path, ParserState& pstate, Import_Ptr imp) + bool call_importers(const std::string& load_path, const char* ctx_path, ParserState& pstate, Import* imp) { return call_loader(load_path, ctx_path, pstate, imp, c_importers, true); }; private: - bool call_loader(const std::string& load_path, const char* ctx_path, ParserState& pstate, Import_Ptr imp, std::vector importers, bool only_one = true); + bool call_loader(const std::string& load_path, const char* ctx_path, ParserState& pstate, Import* imp, std::vector importers, bool only_one = true); public: const std::string CWD; @@ -46,16 +38,16 @@ namespace Sass { // generic ast node garbage container // used to avoid possible circular refs - std::vector ast_gc; + CallStack ast_gc; // resources add under our control // these are guaranteed to be freed std::vector strings; std::vector resources; std::map sheets; - Subset_Map subset_map; - std::vector import_stack; + ImporterStack import_stack; std::vector callee_stack; std::vector traces; + Extender extender; struct Sass_Compiler* c_compiler; @@ -68,10 +60,6 @@ namespace Sass { std::vector plugin_paths; // relative paths to load plugins std::vector include_paths; // lookup paths for includes - - - - void apply_custom_headers(Block_Obj root, const char* path, ParserState pstate); std::vector c_headers; diff --git a/src/libsass/src/cssize.cpp b/src/libsass/src/cssize.cpp index 6a12fdf7b..5c07aaf00 100644 --- a/src/libsass/src/cssize.cpp +++ b/src/libsass/src/cssize.cpp @@ -1,4 +1,7 @@ +// sass.hpp must go before all system headers to get the +// __EXTENSIONS__ fix on Solaris. #include "sass.hpp" + #include #include #include @@ -9,18 +12,17 @@ namespace Sass { Cssize::Cssize(Context& ctx) - : ctx(ctx), - traces(ctx.traces), - block_stack(std::vector()), - p_stack(std::vector()) + : traces(ctx.traces), + block_stack(BlockStack()), + p_stack(std::vector()) { } - Statement_Ptr Cssize::parent() + Statement* Cssize::parent() { return p_stack.size() ? p_stack.back() : block_stack.front(); } - Block_Ptr Cssize::operator()(Block_Ptr b) + Block* Cssize::operator()(Block* b) { Block_Obj bb = SASS_MEMORY_NEW(Block, b->pstate(), b->length(), b->is_root()); // bb->tabs(b->tabs()); @@ -30,7 +32,7 @@ namespace Sass { return bb.detach(); } - Statement_Ptr Cssize::operator()(Trace_Ptr t) + Statement* Cssize::operator()(Trace* t) { traces.push_back(Backtrace(t->pstate())); auto result = t->block()->perform(this); @@ -38,11 +40,11 @@ namespace Sass { return result; } - Statement_Ptr Cssize::operator()(Declaration_Ptr d) + Statement* Cssize::operator()(Declaration* d) { String_Obj property = Cast(d->property()); - if (Declaration_Ptr dd = Cast(parent())) { + if (Declaration* dd = Cast(parent())) { String_Obj parent_property = Cast(dd->property()); property = SASS_MEMORY_NEW(String_Constant, d->property()->pstate(), @@ -78,7 +80,7 @@ namespace Sass { return 0; } - Statement_Ptr Cssize::operator()(Directive_Ptr r) + Statement* Cssize::operator()(Directive* r) { if (!r->block() || !r->block()->length()) return r; @@ -110,10 +112,10 @@ namespace Sass { } - Block_Ptr result = SASS_MEMORY_NEW(Block, rr->pstate()); + Block* result = SASS_MEMORY_NEW(Block, rr->pstate()); if (!(directive_exists || rr->is_keyframes())) { - Directive_Ptr empty_node = Cast(rr); + Directive* empty_node = Cast(rr); empty_node->block(SASS_MEMORY_NEW(Block, rr->block() ? rr->block()->pstate() : rr->pstate())); result->append(empty_node); } @@ -128,7 +130,7 @@ namespace Sass { return result; } - Statement_Ptr Cssize::operator()(Keyframe_Rule_Ptr r) + Statement* Cssize::operator()(Keyframe_Rule* r) { if (!r->block() || !r->block()->length()) return r; @@ -140,14 +142,14 @@ namespace Sass { return debubble(rr->block(), rr); } - Statement_Ptr Cssize::operator()(Ruleset_Ptr r) + Statement* Cssize::operator()(Ruleset* 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 comming from propset expand - Block_Ptr bb = operator()(r->block()); + // 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) { @@ -167,10 +169,10 @@ namespace Sass { } Block_Obj props = SASS_MEMORY_NEW(Block, rr->block()->pstate()); - Block_Ptr rules = 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_Ptr s = rr->block()->at(i); + Statement* s = rr->block()->at(i); if (bubblable(s)) rules->append(s); if (!bubblable(s)) props->append(s); } @@ -183,14 +185,14 @@ namespace Sass { for (size_t i = 0, L = rules->length(); i < L; i++) { - Statement_Ptr stm = rules->at(i); + Statement* stm = rules->at(i); stm->tabs(stm->tabs() + 1); } rules->unshift(rr); } - Block_Ptr ptr = rules; + Block* ptr = rules; rules = debubble(rules); void* lp = ptr; void* rp = rules; @@ -207,25 +209,28 @@ namespace Sass { return rules; } - Statement_Ptr Cssize::operator()(Null_Ptr m) + Statement* Cssize::operator()(Null* m) { return 0; } - Statement_Ptr Cssize::operator()(Media_Block_Ptr m) + Statement* Cssize::operator()(CssMediaRule* m) { if (parent()->statement_type() == Statement::RULESET) - { return bubble(m); } + { + return bubble(m); + } if (parent()->statement_type() == Statement::MEDIA) - { return SASS_MEMORY_NEW(Bubble, m->pstate(), m); } + { + return SASS_MEMORY_NEW(Bubble, m->pstate(), m); + } p_stack.push_back(m); - Media_Block_Obj mm = SASS_MEMORY_NEW(Media_Block, - m->pstate(), - m->media_queries(), - operator()(m->block())); + CssMediaRuleObj mm = SASS_MEMORY_NEW(CssMediaRule, m->pstate(), m->block()); + mm->concat(m->elements()); + mm->block(operator()(m->block())); mm->tabs(m->tabs()); p_stack.pop_back(); @@ -233,7 +238,7 @@ namespace Sass { return debubble(mm->block(), mm); } - Statement_Ptr Cssize::operator()(Supports_Block_Ptr m) + Statement* Cssize::operator()(Supports_Block* m) { if (!m->block()->length()) { return m; } @@ -254,17 +259,17 @@ namespace Sass { return debubble(mm->block(), mm); } - Statement_Ptr Cssize::operator()(At_Root_Block_Ptr m) + Statement* Cssize::operator()(At_Root_Block* m) { bool tmp = false; for (size_t i = 0, L = p_stack.size(); i < L; ++i) { - Statement_Ptr s = p_stack[i]; + Statement* s = p_stack[i]; tmp |= m->exclude_node(s); } if (!tmp && m->block()) { - Block_Ptr bb = operator()(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); @@ -282,9 +287,9 @@ namespace Sass { return bubble(m); } - Statement_Ptr Cssize::bubble(Directive_Ptr m) + Statement* Cssize::bubble(Directive* m) { - Block_Ptr bb = SASS_MEMORY_NEW(Block, this->parent()->pstate()); + Block* bb = SASS_MEMORY_NEW(Block, this->parent()->pstate()); Has_Block_Obj new_rule = Cast(SASS_MEMORY_COPY(this->parent())); new_rule->block(bb); new_rule->tabs(this->parent()->tabs()); @@ -299,16 +304,16 @@ namespace Sass { wrapper_block); if (m->value()) mm->value(m->value()); - Bubble_Ptr bubble = SASS_MEMORY_NEW(Bubble, mm->pstate(), mm); + Bubble* bubble = SASS_MEMORY_NEW(Bubble, mm->pstate(), mm); return bubble; } - Statement_Ptr Cssize::bubble(At_Root_Block_Ptr m) + Statement* Cssize::bubble(At_Root_Block* m) { if (!m || !m->block()) return NULL; - Block_Ptr bb = SASS_MEMORY_NEW(Block, this->parent()->pstate()); + Block* bb = SASS_MEMORY_NEW(Block, this->parent()->pstate()); Has_Block_Obj new_rule = Cast(SASS_MEMORY_COPY(this->parent())); - Block_Ptr wrapper_block = SASS_MEMORY_NEW(Block, m->block()->pstate()); + Block* wrapper_block = SASS_MEMORY_NEW(Block, m->block()->pstate()); if (new_rule) { new_rule->block(bb); new_rule->tabs(this->parent()->tabs()); @@ -316,74 +321,74 @@ namespace Sass { wrapper_block->append(new_rule); } - At_Root_Block_Ptr mm = SASS_MEMORY_NEW(At_Root_Block, + At_Root_Block* mm = SASS_MEMORY_NEW(At_Root_Block, m->pstate(), wrapper_block, m->expression()); - Bubble_Ptr bubble = SASS_MEMORY_NEW(Bubble, mm->pstate(), mm); + Bubble* bubble = SASS_MEMORY_NEW(Bubble, mm->pstate(), mm); return bubble; } - Statement_Ptr Cssize::bubble(Supports_Block_Ptr m) + Statement* Cssize::bubble(Supports_Block* m) { Ruleset_Obj parent = Cast(SASS_MEMORY_COPY(this->parent())); - Block_Ptr bb = SASS_MEMORY_NEW(Block, parent->block()->pstate()); - Ruleset_Ptr new_rule = SASS_MEMORY_NEW(Ruleset, + Block* bb = SASS_MEMORY_NEW(Block, parent->block()->pstate()); + Ruleset* new_rule = SASS_MEMORY_NEW(Ruleset, parent->pstate(), parent->selector(), bb); new_rule->tabs(parent->tabs()); new_rule->block()->concat(m->block()); - Block_Ptr wrapper_block = SASS_MEMORY_NEW(Block, m->block()->pstate()); + Block* wrapper_block = SASS_MEMORY_NEW(Block, m->block()->pstate()); wrapper_block->append(new_rule); - Supports_Block_Ptr mm = SASS_MEMORY_NEW(Supports_Block, + Supports_Block* mm = SASS_MEMORY_NEW(Supports_Block, m->pstate(), m->condition(), wrapper_block); mm->tabs(m->tabs()); - Bubble_Ptr bubble = SASS_MEMORY_NEW(Bubble, mm->pstate(), mm); + Bubble* bubble = SASS_MEMORY_NEW(Bubble, mm->pstate(), mm); return bubble; } - Statement_Ptr Cssize::bubble(Media_Block_Ptr m) + Statement* Cssize::bubble(CssMediaRule* m) { Ruleset_Obj parent = Cast(SASS_MEMORY_COPY(this->parent())); - Block_Ptr bb = SASS_MEMORY_NEW(Block, parent->block()->pstate()); - Ruleset_Ptr new_rule = SASS_MEMORY_NEW(Ruleset, - parent->pstate(), - parent->selector(), - bb); + Block* bb = SASS_MEMORY_NEW(Block, parent->block()->pstate()); + Ruleset* new_rule = SASS_MEMORY_NEW(Ruleset, + parent->pstate(), + parent->selector(), + bb); new_rule->tabs(parent->tabs()); new_rule->block()->concat(m->block()); - Block_Ptr wrapper_block = SASS_MEMORY_NEW(Block, m->block()->pstate()); + Block* wrapper_block = SASS_MEMORY_NEW(Block, m->block()->pstate()); wrapper_block->append(new_rule); - Media_Block_Obj mm = SASS_MEMORY_NEW(Media_Block, - m->pstate(), - m->media_queries(), - wrapper_block); + CssMediaRuleObj mm = SASS_MEMORY_NEW(CssMediaRule, + m->pstate(), + wrapper_block); + mm->concat(m->elements()); mm->tabs(m->tabs()); return SASS_MEMORY_NEW(Bubble, mm->pstate(), mm); } - bool Cssize::bubblable(Statement_Ptr s) + bool Cssize::bubblable(Statement* s) { return Cast(s) || s->bubbles(); } - Block_Ptr Cssize::flatten(Block_Ptr b) + Block* Cssize::flatten(const Block* b) { - Block_Ptr result = SASS_MEMORY_NEW(Block, b->pstate(), 0, b->is_root()); + Block* result = SASS_MEMORY_NEW(Block, b->pstate(), 0, b->is_root()); for (size_t i = 0, L = b->length(); i < L; ++i) { - Statement_Ptr ss = b->at(i); - if (Block_Ptr bb = Cast(ss)) { + 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)); @@ -396,7 +401,7 @@ namespace Sass { return result; } - std::vector> Cssize::slice_by_bubble(Block_Ptr b) + std::vector> Cssize::slice_by_bubble(Block* b) { std::vector> results; @@ -411,7 +416,7 @@ namespace Sass { } else { - Block_Ptr wrapper_block = SASS_MEMORY_NEW(Block, value->pstate()); + Block* wrapper_block = SASS_MEMORY_NEW(Block, value->pstate()); wrapper_block->append(value); results.push_back(std::make_pair(key, wrapper_block)); } @@ -419,9 +424,9 @@ namespace Sass { return results; } - Block_Ptr Cssize::debubble(Block_Ptr children, Statement_Ptr parent) + Block* Cssize::debubble(Block* children, Statement* parent) { - Has_Block_Obj previous_parent = 0; + Has_Block_Obj previous_parent; std::vector> baz = slice_by_bubble(children); Block_Obj result = SASS_MEMORY_NEW(Block, children->pstate()); @@ -437,7 +442,7 @@ namespace Sass { previous_parent->block()->concat(slice); } else { - previous_parent = Cast(SASS_MEMORY_COPY(parent)); + previous_parent = SASS_MEMORY_COPY(parent); previous_parent->block(slice); previous_parent->tabs(parent->tabs()); @@ -448,36 +453,24 @@ namespace Sass { for (size_t j = 0, K = slice->length(); j < K; ++j) { - Statement_Ptr ss; + Statement_Obj ss; Statement_Obj stm = slice->at(j); // this has to go now here (too bad) Bubble_Obj node = Cast(stm); - Media_Block_Ptr m1 = NULL; - Media_Block_Ptr m2 = NULL; - if (parent) m1 = Cast(parent); - if (node) m2 = Cast(node->node()); - if (!parent || - parent->statement_type() != Statement::MEDIA || - node->node()->statement_type() != Statement::MEDIA || - (m1 && m2 && *m1->media_queries() == *m2->media_queries()) - ) - { - ss = node->node(); - } - else - { - List_Obj mq = merge_media_queries( - Cast(node->node()), - Cast(parent) - ); - if (!mq->length()) continue; - if (Media_Block* b = Cast(node->node())) { - b->media_queries(mq); - } + + CssMediaRule* rule1 = NULL; + CssMediaRule* rule2 = NULL; + if (parent) rule1 = Cast(parent); + if (node) rule2 = Cast(node->node()); + if (rule1 || rule2) { ss = node->node(); } - if (!ss) continue; + ss = node->node(); + + if (!ss) { + continue; + } ss->tabs(ss->tabs() + node->tabs()); ss->group_end(node->group_end()); @@ -493,11 +486,11 @@ namespace Sass { children->length(), children->is_root()); - Block_Ptr wrapper = flatten(bb); + Block* wrapper = flatten(bb); wrapper_block->append(wrapper); if (wrapper->length()) { - previous_parent = NULL; + previous_parent = {}; } if (wrapper_block) { @@ -509,16 +502,11 @@ namespace Sass { return flatten(result); } - Statement_Ptr Cssize::fallback_impl(AST_Node_Ptr n) - { - return static_cast(n); - } - - void Cssize::append_block(Block_Ptr b, Block_Ptr cur) + 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_Ptr bb = Cast(ith)) { + if (Block_Obj bb = Cast(ith)) { for (size_t j = 0, K = bb->length(); j < K; ++j) { cur->append(bb->at(j)); } @@ -529,78 +517,4 @@ namespace Sass { } } - List_Ptr Cssize::merge_media_queries(Media_Block_Ptr m1, Media_Block_Ptr m2) - { - List_Ptr qq = SASS_MEMORY_NEW(List, - m1->media_queries()->pstate(), - m1->media_queries()->length(), - SASS_COMMA); - - for (size_t i = 0, L = m1->media_queries()->length(); i < L; i++) { - for (size_t j = 0, K = m2->media_queries()->length(); j < K; j++) { - Expression_Obj l1 = m1->media_queries()->at(i); - Expression_Obj l2 = m2->media_queries()->at(j); - Media_Query_Ptr mq1 = Cast(l1); - Media_Query_Ptr mq2 = Cast(l2); - Media_Query_Ptr mq = merge_media_query(mq1, mq2); - if (mq) qq->append(mq); - } - } - - return qq; - } - - - Media_Query_Ptr Cssize::merge_media_query(Media_Query_Ptr mq1, Media_Query_Ptr mq2) - { - - std::string type; - std::string mod; - - std::string m1 = std::string(mq1->is_restricted() ? "only" : mq1->is_negated() ? "not" : ""); - std::string t1 = mq1->media_type() ? mq1->media_type()->to_string(ctx.c_options) : ""; - std::string m2 = std::string(mq2->is_restricted() ? "only" : mq2->is_negated() ? "not" : ""); - std::string t2 = mq2->media_type() ? mq2->media_type()->to_string(ctx.c_options) : ""; - - - if (t1.empty()) t1 = t2; - if (t2.empty()) t2 = t1; - - if ((m1 == "not") ^ (m2 == "not")) { - if (t1 == t2) { - return 0; - } - type = m1 == "not" ? t2 : t1; - mod = m1 == "not" ? m2 : m1; - } - else if (m1 == "not" && m2 == "not") { - if (t1 != t2) { - return 0; - } - type = t1; - mod = "not"; - } - else if (t1 != t2) { - return 0; - } else { - type = t1; - mod = m1.empty() ? m2 : m1; - } - - Media_Query_Ptr mm = SASS_MEMORY_NEW(Media_Query, - mq1->pstate(), - 0, - mq1->length() + mq2->length(), - mod == "not", - mod == "only"); - - if (!type.empty()) { - mm->media_type(SASS_MEMORY_NEW(String_Quoted, mq1->pstate(), type)); - } - - mm->concat(mq2); - mm->concat(mq1); - - return mm; - } } diff --git a/src/libsass/src/cssize.hpp b/src/libsass/src/cssize.hpp index 5a6c704b0..bc4aedd9d 100644 --- a/src/libsass/src/cssize.hpp +++ b/src/libsass/src/cssize.hpp @@ -10,66 +10,60 @@ namespace Sass { struct Backtrace; - class Cssize : public Operation_CRTP { + class Cssize : public Operation_CRTP { - Context& ctx; Backtraces& traces; - std::vector block_stack; - std::vector p_stack; - - Statement_Ptr fallback_impl(AST_Node_Ptr n); + BlockStack block_stack; + std::vector p_stack; public: Cssize(Context&); ~Cssize() { } - Selector_List_Ptr selector(); - - Block_Ptr operator()(Block_Ptr); - Statement_Ptr operator()(Ruleset_Ptr); - // Statement_Ptr operator()(Bubble_Ptr); - Statement_Ptr operator()(Media_Block_Ptr); - Statement_Ptr operator()(Supports_Block_Ptr); - Statement_Ptr operator()(At_Root_Block_Ptr); - Statement_Ptr operator()(Directive_Ptr); - Statement_Ptr operator()(Keyframe_Rule_Ptr); - Statement_Ptr operator()(Trace_Ptr); - Statement_Ptr operator()(Declaration_Ptr); - // Statement_Ptr operator()(Assignment_Ptr); - // Statement_Ptr operator()(Import_Ptr); - // Statement_Ptr operator()(Import_Stub_Ptr); - // Statement_Ptr operator()(Warning_Ptr); - // Statement_Ptr operator()(Error_Ptr); - // Statement_Ptr operator()(Comment_Ptr); - // Statement_Ptr operator()(If_Ptr); - // Statement_Ptr operator()(For_Ptr); - // Statement_Ptr operator()(Each_Ptr); - // Statement_Ptr operator()(While_Ptr); - // Statement_Ptr operator()(Return_Ptr); - // Statement_Ptr operator()(Extension_Ptr); - // Statement_Ptr operator()(Definition_Ptr); - // Statement_Ptr operator()(Mixin_Call_Ptr); - // Statement_Ptr operator()(Content_Ptr); - Statement_Ptr operator()(Null_Ptr); - - Statement_Ptr parent(); - std::vector> slice_by_bubble(Block_Ptr); - Statement_Ptr bubble(Directive_Ptr); - Statement_Ptr bubble(At_Root_Block_Ptr); - Statement_Ptr bubble(Media_Block_Ptr); - Statement_Ptr bubble(Supports_Block_Ptr); - - Block_Ptr debubble(Block_Ptr children, Statement_Ptr parent = 0); - Block_Ptr flatten(Block_Ptr); - bool bubblable(Statement_Ptr); - - List_Ptr merge_media_queries(Media_Block_Ptr, Media_Block_Ptr); - Media_Query_Ptr merge_media_query(Media_Query_Ptr, Media_Query_Ptr); - + Block* operator()(Block*); + Statement* operator()(Ruleset*); + // Statement* operator()(Bubble*); + Statement* operator()(CssMediaRule*); + Statement* operator()(Supports_Block*); + Statement* operator()(At_Root_Block*); + Statement* operator()(Directive*); + Statement* operator()(Keyframe_Rule*); + Statement* operator()(Trace*); + Statement* operator()(Declaration*); + // Statement* operator()(Assignment*); + // Statement* operator()(Import*); + // Statement* operator()(Import_Stub*); + // Statement* operator()(Warning*); + // Statement* operator()(Error*); + // Statement* operator()(Comment*); + // Statement* operator()(If*); + // Statement* operator()(For*); + // Statement* operator()(Each*); + // Statement* operator()(While*); + // Statement* operator()(Return*); + // Statement* operator()(ExtendRule*); + // Statement* operator()(Definition*); + // Statement* operator()(Mixin_Call*); + // Statement* operator()(Content*); + Statement* operator()(Null*); + + Statement* parent(); + std::vector> slice_by_bubble(Block*); + Statement* bubble(Directive*); + Statement* bubble(At_Root_Block*); + Statement* bubble(CssMediaRule*); + Statement* bubble(Supports_Block*); + + Block* debubble(Block* children, Statement* parent = 0); + Block* flatten(const Block*); + bool bubblable(Statement*); + + // generic fallback template - Statement_Ptr fallback(U x) { return fallback_impl(x); } + Statement* fallback(U x) + { return Cast(x); } - void append_block(Block_Ptr, Block_Ptr); + void append_block(Block*, Block*); }; } diff --git a/src/libsass/src/dart_helpers.hpp b/src/libsass/src/dart_helpers.hpp new file mode 100644 index 000000000..d5bd75941 --- /dev/null +++ b/src/libsass/src/dart_helpers.hpp @@ -0,0 +1,199 @@ +#ifndef SASS_DART_HELPERS_H +#define SASS_DART_HELPERS_H + +#include +#include +#include +#include + +namespace Sass { + + // ########################################################################## + // Flatten `vector>` to `vector` + // ########################################################################## + template + T flatten(const std::vector& all) + { + T flattened; + for (const auto& sub : all) { + std::copy(std::begin(sub), std::end(sub), + std::back_inserter(flattened)); + } + return flattened; + } + + // ########################################################################## + // Expands each element of this Iterable into zero or more elements. + // Calls a function on every element and ads all results to flat array + // ########################################################################## + // Equivalent to dart `cnt.any` + // Pass additional closure variables to `fn` + template + T expand(const T& cnt, U fn, Args... args) { + T flattened; + for (const auto& sub : cnt) { + auto rv = fn(sub, args...); + flattened.insert(flattened.end(), + rv.begin(), rv.end()); + } + return flattened; + } + + // ########################################################################## + // ########################################################################## + template + T flattenInner(const std::vector& vec) + { + T outer; + for (const auto& sub : vec) { + outer.emplace_back(std::move(flatten(sub))); + } + return outer; + } + // EO flattenInner + + // ########################################################################## + // Equivalent to dart `cnt.any` + // Pass additional closure variables to `fn` + // ########################################################################## + template + bool hasAny(const T& cnt, U fn, Args... args) { + for (const auto& sub : cnt) { + if (fn(sub, args...)) { + return true; + } + } + return false; + } + // EO hasAny + + // ########################################################################## + // Equivalent to dart `cnt.take(len).any` + // Pass additional closure variables to `fn` + // ########################################################################## + template + bool hasSubAny(const T& cnt, size_t len, U fn, Args... args) { + for (size_t i = 0; i < len; i++) { + if (fn(cnt[i], args...)) { + return true; + } + } + return false; + } + + // ########################################################################## + // Default predicate for lcs algorithm + // ########################################################################## + template + inline bool lcsIdentityCmp(const T& X, const T& Y, T& result) + { + // Assert equality + if (!ObjEqualityFn(X, Y)) { + return false; + } + // Store in reference + result = X; + // Return success + return true; + } + // EO lcsIdentityCmp + + // ########################################################################## + // Longest common subsequence with predicate + // ########################################################################## + template + std::vector lcs( + const std::vector& X, const std::vector& Y, + bool(*select)(const T&, const T&, T&) = lcsIdentityCmp) + { + + std::size_t m = X.size(), mm = X.size() + 1; + std::size_t n = Y.size(), nn = Y.size() + 1; + + if (m == 0) return {}; + if (n == 0) return {}; + + // MSVC does not support variable-length arrays + // To circumvent, allocate one array on the heap + // Then use a macro to access via double index + // e.g. `size_t L[m][n]` is supported by gcc + size_t* len = new size_t[mm * nn + 1]; + bool* acc = new bool[mm * nn + 1]; + T* res = new T[mm * nn + 1]; + + #define LEN(x, y) len[(x) * nn + (y)] + #define ACC(x, y) acc[(x) * nn + (y)] + #define RES(x, y) res[(x) * nn + (y)] + + /* Following steps build L[m+1][n+1] in bottom up fashion. Note + that L[i][j] contains length of LCS of X[0..i-1] and Y[0..j-1] */ + for (size_t i = 0; i <= m; i++) { + for (size_t j = 0; j <= n; j++) { + if (i == 0 || j == 0) + LEN(i, j) = 0; + else { + ACC(i - 1, j - 1) = select(X[i - 1], Y[j - 1], RES(i - 1, j - 1)); + if (ACC(i - 1, j - 1)) + LEN(i, j) = LEN(i - 1, j - 1) + 1; + else + LEN(i, j) = std::max(LEN(i - 1, j), LEN(i, j - 1)); + } + } + } + + // Following code is used to print LCS + std::vector lcs; + std::size_t index = LEN(m, n); + lcs.reserve(index); + + // Start from the right-most-bottom-most corner + // and one by one store objects in lcs[] + std::size_t i = m, j = n; + while (i > 0 && j > 0) { + + // If current objects in X[] and Y are same, + // then current object is part of LCS + if (ACC(i - 1, j - 1)) + { + // Put the stored object in result + // Note: we push instead of unshift + // Note: reverse the vector later + // ToDo: is deque more performant? + lcs.push_back(RES(i - 1, j - 1)); + // reduce values of i, j and index + i -= 1; j -= 1; index -= 1; + } + + // If not same, then find the larger of two and + // go in the direction of larger value + else if (LEN(i - 1, j) > LEN(i, j - 1)) { + i--; + } + else { + j--; + } + + } + + // reverse now as we used push_back + std::reverse(lcs.begin(), lcs.end()); + + // Delete temp memory on heap + delete[] len; + delete[] acc; + delete[] res; + + #undef LEN + #undef ACC + #undef RES + + return lcs; + } + // EO lcs + + // ########################################################################## + // ########################################################################## + +} + +#endif diff --git a/src/libsass/src/debugger.hpp b/src/libsass/src/debugger.hpp index f1ceabd9a..62a645e53 100644 --- a/src/libsass/src/debugger.hpp +++ b/src/libsass/src/debugger.hpp @@ -1,27 +1,286 @@ #ifndef SASS_DEBUGGER_H #define SASS_DEBUGGER_H +// sass.hpp must go before all system headers to get the +// __EXTENSIONS__ fix on Solaris. +#include "sass.hpp" + +#include +#include #include #include -#include "node.hpp" +#include "ast.hpp" #include "ast_fwd_decl.hpp" +#include "extension.hpp" + +#include "ordered_map.hpp" using namespace Sass; -inline void debug_ast(AST_Node_Ptr node, std::string ind = "", Env* env = 0); +inline void debug_ast(AST_Node* node, std::string ind = "", Env* env = 0); -inline void debug_ast(const AST_Node* node, std::string ind = "", Env* env = 0) { - debug_ast(const_cast(node), ind, env); +inline std::string debug_vec(const AST_Node* node) { + if (node == NULL) return "null"; + else return node->to_string(); } -inline void debug_sources_set(ComplexSelectorSet& set, std::string ind = "") -{ - if (ind == "") std::cerr << "#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n"; - for(auto const &pair : set) { - debug_ast(pair, ind + ""); - // debug_ast(set[pair], ind + "first: "); +inline std::string debug_dude(std::vector> vec) { + std::stringstream out; + out << "{"; + bool joinOut = false; + for (auto ct : vec) { + if (joinOut) out << ", "; + joinOut = true; + out << "{"; + bool joinIn = false; + for (auto nr : ct) { + if (joinIn) out << ", "; + joinIn = true; + out << nr; + } + out << "}"; + } + out << "}"; + return out.str(); +} + +inline std::string debug_vec(std::string& str) { + return str; +} + +inline std::string debug_vec(Extension& ext) { + std::stringstream out; + out << debug_vec(ext.extender); + out << " {@extend "; + out << debug_vec(ext.target); + if (ext.isOptional) { + out << " !optional"; + } + out << "}"; + return out.str(); +} + +template +inline std::string debug_vec(std::vector vec) { + std::stringstream out; + out << "["; + for (size_t i = 0; i < vec.size(); i += 1) { + if (i > 0) out << ", "; + out << debug_vec(vec[i]); + } + out << "]"; + return out.str(); +} + +template +inline std::string debug_vec(std::queue vec) { + std::stringstream out; + out << "{"; + for (size_t i = 0; i < vec.size(); i += 1) { + if (i > 0) out << ", "; + out << debug_vec(vec[i]); + } + out << "}"; + return out.str(); +} + +template +inline std::string debug_vec(std::map vec) { + std::stringstream out; + out << "{"; + bool joinit = false; + for (auto it = vec.begin(); it != vec.end(); it++) + { + if (joinit) out << ", "; + out << debug_vec(it->first) // string (key) + << ": " + << debug_vec(it->second); // string's value + joinit = true; + } + out << "}"; + return out.str(); +} + +template +inline std::string debug_vec(const ordered_map& vec) { + std::stringstream out; + out << "{"; + bool joinit = false; + for (auto it = vec.begin(); it != vec.end(); it++) + { + if (joinit) out << ", "; + out << debug_vec(*it); // string (key) + // << debug_vec(it->second); // string's value + joinit = true; + } + out << "}"; + return out.str(); +} + +template +inline std::string debug_vec(std::unordered_map vec) { + std::stringstream out; + out << "{"; + bool joinit = false; + for (auto it = vec.begin(); it != vec.end(); it++) + { + if (joinit) out << ", "; + out << debug_vec(it->first) // string (key) + << ": " + << debug_vec(it->second); // string's value + joinit = true; + } + out << "}"; + return out.str(); +} + +template +inline std::string debug_keys(std::unordered_map vec) { + std::stringstream out; + out << "{"; + bool joinit = false; + for (auto it = vec.begin(); it != vec.end(); it++) + { + if (joinit) out << ", "; + out << debug_vec(it->first); // string (key) + joinit = true; } - if (ind == "") std::cerr << "#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n"; + out << "}"; + return out.str(); +} + +inline std::string debug_vec(ExtListSelSet& vec) { + std::stringstream out; + out << "{"; + bool joinit = false; + for (auto it = vec.begin(); it != vec.end(); it++) + { + if (joinit) out << ", "; + out << debug_vec(*it); // string (key) + joinit = true; + } + out << "}"; + return out.str(); +} + +/* +template +inline std::string debug_values(tsl::ordered_map vec) { + std::stringstream out; + out << "{"; + bool joinit = false; + for (auto it = vec.begin(); it != vec.end(); it++) + { + if (joinit) out << ", "; + out << debug_vec(const_cast(it->second)); // string's value + joinit = true; + } + out << "}"; + return out.str(); +} + +template +inline std::string debug_vec(tsl::ordered_map vec) { + std::stringstream out; + out << "{"; + bool joinit = false; + for (auto it = vec.begin(); it != vec.end(); it++) + { + if (joinit) out << ", "; + out << debug_vec(it->first) // string (key) + << ": " + << debug_vec(const_cast(it->second)); // string's value + joinit = true; + } + out << "}"; + return out.str(); +} + +template +inline std::string debug_vals(tsl::ordered_map vec) { + std::stringstream out; + out << "{"; + bool joinit = false; + for (auto it = vec.begin(); it != vec.end(); it++) + { + if (joinit) out << ", "; + out << debug_vec(const_cast(it->second)); // string's value + joinit = true; + } + out << "}"; + return out.str(); +} + +template +inline std::string debug_keys(tsl::ordered_map vec) { + std::stringstream out; + out << "{"; + bool joinit = false; + for (auto it = vec.begin(); it != vec.end(); it++) + { + if (joinit) out << ", "; + out << debug_vec(it->first); + joinit = true; + } + out << "}"; + return out.str(); +} +*/ + +template +inline std::string debug_vec(std::set vec) { + std::stringstream out; + out << "{"; + bool joinit = false; + for (auto item : vec) { + if (joinit) out << ", "; + out << debug_vec(item); + joinit = true; + } + out << "}"; + return out.str(); +} + +/* +template +inline std::string debug_vec(tsl::ordered_set vec) { + std::stringstream out; + out << "{"; + bool joinit = false; + for (auto item : vec) { + if (joinit) out << ", "; + out << debug_vec(item); + joinit = true; + } + out << "}"; + return out.str(); +} +*/ + +template +inline std::string debug_vec(std::unordered_set vec) { + std::stringstream out; + out << "{"; + bool joinit = false; + for (auto item : vec) { + if (joinit) out << ", "; + out << debug_vec(item); + joinit = true; + } + out << "}"; + return out.str(); +} + +inline std::string debug_bool(bool val) { + return val ? "true" : "false"; +} +inline std::string debug_vec(ExtSmplSelSet* node) { + if (node == NULL) return "null"; + else return debug_vec(*node); +} + +inline void debug_ast(const AST_Node* node, std::string ind = "", Env* env = 0) { + debug_ast(const_cast(node), ind, env); } inline std::string str_replace(std::string str, const std::string& oldStr, const std::string& newStr) @@ -48,12 +307,12 @@ inline std::string longToHex(long long t) { return is.str(); } -inline std::string pstate_source_position(AST_Node_Ptr node) +inline std::string pstate_source_position(AST_Node* node) { std::stringstream str; Position start(node->pstate()); Position end(start + node->pstate().offset); - str << (start.file == std::string::npos ? -1 : start.file) + str << (start.file == std::string::npos ? 99999999 : start.file) << "@[" << start.line << ":" << start.column << "]" << "-[" << end.line << ":" << end.column << "]"; #ifdef DEBUG_SHARED_PTR @@ -64,203 +323,163 @@ inline std::string pstate_source_position(AST_Node_Ptr node) return str.str(); } -inline void debug_ast(AST_Node_Ptr node, std::string ind, Env* env) +inline void debug_ast(AST_Node* node, std::string ind, Env* env) { if (node == 0) return; if (ind == "") std::cerr << "####################################################################\n"; if (Cast(node)) { - Bubble_Ptr bubble = Cast(node); + Bubble* bubble = Cast(node); std::cerr << ind << "Bubble " << bubble; std::cerr << " (" << pstate_source_position(node) << ")"; std::cerr << " " << bubble->tabs(); std::cerr << std::endl; debug_ast(bubble->node(), ind + " ", env); } else if (Cast(node)) { - Trace_Ptr trace = 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)) { - At_Root_Block_Ptr root_block = Cast(node); + At_Root_Block* root_block = Cast(node); std::cerr << ind << "At_Root_Block " << 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)) { - Selector_List_Ptr selector = Cast(node); - std::cerr << ind << "Selector_List " << selector; + } else if (Cast(node)) { + SelectorList* selector = Cast(node); + std::cerr << ind << "SelectorList " << selector; std::cerr << " (" << pstate_source_position(node) << ")"; std::cerr << " <" << selector->hash() << ">"; - std::cerr << " [@media:" << selector->media_block() << "]"; - std::cerr << (selector->is_invisible() ? " [INVISIBLE]": " -"); - std::cerr << (selector->has_placeholder() ? " [PLACEHOLDER]": " -"); - std::cerr << (selector->is_optional() ? " [is_optional]": " -"); - std::cerr << (selector->has_parent_ref() ? " [has-parent]": " -"); - std::cerr << (selector->has_line_break() ? " [line-break]": " -"); - std::cerr << (selector->has_line_feed() ? " [line-feed]": " -"); + std::cerr << (selector->is_invisible() ? " [is_invisible]" : " -"); + std::cerr << (selector->isInvisible() ? " [isInvisible]" : " -"); + std::cerr << (selector->has_real_parent_ref() ? " [real-parent]": " -"); std::cerr << std::endl; - debug_ast(selector->schema(), ind + "#{} "); - for(const Complex_Selector_Obj& i : selector->elements()) { debug_ast(i, ind + " ", env); } + for(const ComplexSelector_Obj& i : selector->elements()) { debug_ast(i, ind + " ", env); } -// } else if (Cast(node)) { -// Expression_Ptr expression = Cast(node); -// std::cerr << ind << "Expression " << expression << " " << expression->concrete_type() << std::endl; + } else if (Cast(node)) { + ComplexSelector* selector = Cast(node); + std::cerr << ind << "ComplexSelector " << selector + << " (" << pstate_source_position(node) << ")" + << " <" << selector->hash() << ">" + << " [" << (selector->chroots() ? "CHROOT" : "CONNECT") << "]" + << " [length:" << longToHex(selector->length()) << "]" + << " [weight:" << longToHex(selector->specificity()) << "]" + << (selector->is_invisible() ? " [is_invisible]" : " -") + << (selector->isInvisible() ? " [isInvisible]" : " -") + << (selector->hasPreLineFeed() ? " [hasPreLineFeed]" : " -") - } else if (Cast(node)) { - Parent_Selector_Ptr selector = Cast(node); - std::cerr << ind << "Parent_Selector " << selector; -// if (selector->not_selector()) cerr << " [in_declaration]"; - std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << " <" << selector->hash() << ">"; - std::cerr << " [" << (selector->is_real_parent_ref() ? "REAL" : "FAKE") << "]"; - std::cerr << " <" << prettyprint(selector->pstate().token.ws_before()) << ">" << std::endl; -// debug_ast(selector->selector(), ind + "->", env); + // << (selector->is_invisible() ? " [INVISIBLE]": " -") + // << (selector->has_placeholder() ? " [PLACEHOLDER]": " -") + // << (selector->is_optional() ? " [is_optional]": " -") + << (selector->has_real_parent_ref() ? " [real parent]": " -") + // << (selector->has_line_feed() ? " [line-feed]": " -") + // << (selector->has_line_break() ? " [line-break]": " -") + << " -- \n"; + + for(const SelectorComponentObj& i : selector->elements()) { debug_ast(i, ind + " ", env); } - } else if (Cast(node)) { - Complex_Selector_Ptr selector = Cast(node); - std::cerr << ind << "Complex_Selector " << selector + } else if (Cast(node)) { + SelectorCombinator* selector = Cast(node); + std::cerr << ind << "SelectorCombinator " << selector << " (" << pstate_source_position(node) << ")" << " <" << selector->hash() << ">" - << " [length:" << longToHex(selector->length()) << "]" << " [weight:" << longToHex(selector->specificity()) << "]" - << " [@media:" << selector->media_block() << "]" - << (selector->is_invisible() ? " [INVISIBLE]": " -") - << (selector->has_placeholder() ? " [PLACEHOLDER]": " -") - << (selector->is_optional() ? " [is_optional]": " -") - << (selector->has_parent_ref() ? " [has parent]": " -") - << (selector->has_line_feed() ? " [line-feed]": " -") - << (selector->has_line_break() ? " [line-break]": " -") + << (selector->has_real_parent_ref() ? " [real parent]": " -") << " -- "; + std::string del; switch (selector->combinator()) { - case Complex_Selector::PARENT_OF: del = ">"; break; - case Complex_Selector::PRECEDES: del = "~"; break; - case Complex_Selector::ADJACENT_TO: del = "+"; break; - case Complex_Selector::ANCESTOR_OF: del = " "; break; - case Complex_Selector::REFERENCE: del = "//"; break; + case SelectorCombinator::CHILD: del = ">"; break; + case SelectorCombinator::GENERAL: del = "~"; break; + case SelectorCombinator::ADJACENT: del = "+"; break; } - // if (del = "/") del += selector->reference()->perform(&to_string) + "/"; - std::cerr << " <" << prettyprint(selector->pstate().token.ws_before()) << ">" << std::endl; - debug_ast(selector->head(), ind + " " /* + "[" + del + "]" */, env); - if (selector->tail()) { - debug_ast(selector->tail(), ind + "{" + del + "}", env); - } else if(del != " ") { - std::cerr << ind << " |" << del << "| {trailing op}" << std::endl; - } - ComplexSelectorSet set = selector->sources(); - // debug_sources_set(set, ind + " @--> "); - } else if (Cast(node)) { - Compound_Selector_Ptr selector = Cast(node); - std::cerr << ind << "Compound_Selector " << selector; + + std::cerr << "[" << del << "]" << "\n"; + + } else if (Cast(node)) { + CompoundSelector* selector = Cast(node); + std::cerr << ind << "CompoundSelector " << selector; std::cerr << " (" << pstate_source_position(node) << ")"; std::cerr << " <" << selector->hash() << ">"; + std::cerr << (selector->hasRealParent() ? " [REAL PARENT]" : "") << ">"; std::cerr << " [weight:" << longToHex(selector->specificity()) << "]"; - std::cerr << " [@media:" << selector->media_block() << "]"; - std::cerr << (selector->extended() ? " [extended]": " -"); - std::cerr << (selector->is_optional() ? " [is_optional]": " -"); - std::cerr << (selector->has_parent_ref() ? " [has-parent]": " -"); - std::cerr << (selector->has_line_break() ? " [line-break]": " -"); - std::cerr << (selector->has_line_feed() ? " [line-feed]": " -"); - std::cerr << " <" << prettyprint(selector->pstate().token.ws_before()) << ">" << std::endl; - for(const Simple_Selector_Obj& i : selector->elements()) { debug_ast(i, ind + " ", env); } - } else if (Cast(node)) { - Wrapped_Selector_Ptr selector = Cast(node); - std::cerr << ind << "Wrapped_Selector " << selector; + std::cerr << (selector->hasPostLineBreak() ? " [hasPostLineBreak]" : " -"); + std::cerr << (selector->is_invisible() ? " [is_invisible]" : " -"); + std::cerr << (selector->isInvisible() ? " [isInvisible]" : " -"); + std::cerr << "\n"; + for(const SimpleSelector_Obj& i : selector->elements()) { debug_ast(i, ind + " ", env); } + + } else if (Cast(node)) { + Parent_Reference* selector = Cast(node); + std::cerr << ind << "Parent_Reference " << selector; std::cerr << " (" << pstate_source_position(node) << ")"; std::cerr << " <" << selector->hash() << ">"; - std::cerr << " <<" << selector->ns_name() << ">>"; - std::cerr << (selector->is_optional() ? " [is_optional]": " -"); - std::cerr << (selector->has_parent_ref() ? " [has-parent]": " -"); - std::cerr << (selector->has_line_break() ? " [line-break]": " -"); - std::cerr << (selector->has_line_feed() ? " [line-feed]": " -"); - std::cerr << std::endl; - debug_ast(selector->selector(), ind + " () ", env); + std::cerr << " <" << prettyprint(selector->pstate().token.ws_before()) << ">" << std::endl; + } else if (Cast(node)) { - Pseudo_Selector_Ptr selector = Cast(node); + Pseudo_Selector* selector = Cast(node); std::cerr << ind << "Pseudo_Selector " << selector; std::cerr << " (" << pstate_source_position(node) << ")"; std::cerr << " <" << selector->hash() << ">"; std::cerr << " <<" << selector->ns_name() << ">>"; - std::cerr << (selector->is_optional() ? " [is_optional]": " -"); - std::cerr << (selector->has_parent_ref() ? " [has-parent]": " -"); - std::cerr << (selector->has_line_break() ? " [line-break]": " -"); - std::cerr << (selector->has_line_feed() ? " [line-feed]": " -"); + std::cerr << (selector->isClass() ? " [isClass]": " -"); + std::cerr << (selector->isSyntacticClass() ? " [isSyntacticClass]": " -"); std::cerr << std::endl; - debug_ast(selector->expression(), ind + " <= ", env); + debug_ast(selector->argument(), ind + " <= ", env); + debug_ast(selector->selector(), ind + " || ", env); } else if (Cast(node)) { - Attribute_Selector_Ptr selector = Cast(node); + Attribute_Selector* selector = Cast(node); std::cerr << ind << "Attribute_Selector " << selector; std::cerr << " (" << pstate_source_position(node) << ")"; std::cerr << " <" << selector->hash() << ">"; std::cerr << " <<" << selector->ns_name() << ">>"; - std::cerr << (selector->is_optional() ? " [is_optional]": " -"); - std::cerr << (selector->has_parent_ref() ? " [has-parent]": " -"); - std::cerr << (selector->has_line_break() ? " [line-break]": " -"); - std::cerr << (selector->has_line_feed() ? " [line-feed]": " -"); std::cerr << std::endl; debug_ast(selector->value(), ind + "[" + selector->matcher() + "] ", env); } else if (Cast(node)) { - Class_Selector_Ptr selector = Cast(node); + Class_Selector* selector = Cast(node); std::cerr << ind << "Class_Selector " << selector; std::cerr << " (" << pstate_source_position(node) << ")"; std::cerr << " <" << selector->hash() << ">"; std::cerr << " <<" << selector->ns_name() << ">>"; - std::cerr << (selector->is_optional() ? " [is_optional]": " -"); - std::cerr << (selector->has_parent_ref() ? " [has-parent]": " -"); - std::cerr << (selector->has_line_break() ? " [line-break]": " -"); - std::cerr << (selector->has_line_feed() ? " [line-feed]": " -"); std::cerr << std::endl; } else if (Cast(node)) { - Id_Selector_Ptr selector = Cast(node); + Id_Selector* selector = Cast(node); std::cerr << ind << "Id_Selector " << selector; std::cerr << " (" << pstate_source_position(node) << ")"; std::cerr << " <" << selector->hash() << ">"; std::cerr << " <<" << selector->ns_name() << ">>"; - std::cerr << (selector->is_optional() ? " [is_optional]": " -"); - std::cerr << (selector->has_parent_ref() ? " [has-parent]": " -"); - std::cerr << (selector->has_line_break() ? " [line-break]": " -"); - std::cerr << (selector->has_line_feed() ? " [line-feed]": " -"); std::cerr << std::endl; - } else if (Cast(node)) { - Element_Selector_Ptr selector = Cast(node); - std::cerr << ind << "Element_Selector " << selector; + } else if (Cast(node)) { + Type_Selector* selector = Cast(node); + std::cerr << ind << "Type_Selector " << selector; std::cerr << " (" << pstate_source_position(node) << ")"; std::cerr << " <" << selector->hash() << ">"; std::cerr << " <<" << selector->ns_name() << ">>"; - std::cerr << (selector->is_optional() ? " [is_optional]": " -"); - std::cerr << (selector->has_parent_ref() ? " [has-parent]": " -"); - std::cerr << (selector->has_line_break() ? " [line-break]": " -"); - std::cerr << (selector->has_line_feed() ? " [line-feed]": " -"); std::cerr << " <" << prettyprint(selector->pstate().token.ws_before()) << ">"; std::cerr << std::endl; } else if (Cast(node)) { - Placeholder_Selector_Ptr selector = Cast(node); + Placeholder_Selector* selector = Cast(node); std::cerr << ind << "Placeholder_Selector [" << selector->ns_name() << "] " << selector; std::cerr << " (" << pstate_source_position(selector) << ")" << " <" << selector->hash() << ">" - << " [@media:" << selector->media_block() << "]" - << (selector->is_optional() ? " [is_optional]": " -") - << (selector->has_line_break() ? " [line-break]": " -") - << (selector->has_line_feed() ? " [line-feed]": " -") + << (selector->isInvisible() ? " [isInvisible]" : " -") << std::endl; - } else if (Cast(node)) { - Simple_Selector* selector = Cast(node); - std::cerr << ind << "Simple_Selector " << selector; + } else if (Cast(node)) { + SimpleSelector* selector = Cast(node); + std::cerr << ind << "SimpleSelector " << selector; std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << (selector->has_line_break() ? " [line-break]": " -") << (selector->has_line_feed() ? " [line-feed]": " -") << std::endl; } else if (Cast(node)) { - Selector_Schema_Ptr selector = Cast(node); + Selector_Schema* selector = Cast(node); std::cerr << ind << "Selector_Schema " << selector; std::cerr << " (" << pstate_source_position(node) << ")" - << " [@media:" << selector->media_block() << "]" << (selector->connect_parent() ? " [connect-parent]": " -") << std::endl; @@ -268,15 +487,13 @@ inline void debug_ast(AST_Node_Ptr node, std::string ind, Env* env) // for(auto i : selector->elements()) { debug_ast(i, ind + " ", env); } } else if (Cast(node)) { - Selector_Ptr selector = Cast(node); + Selector* selector = Cast(node); std::cerr << ind << "Selector " << selector; - std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << (selector->has_line_break() ? " [line-break]": " -") - << (selector->has_line_feed() ? " [line-feed]": " -") + std::cerr << " (" << pstate_source_position(node) << ")" << std::endl; } else if (Cast(node)) { - Media_Query_Expression_Ptr block = 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]": " -") @@ -285,7 +502,7 @@ inline void debug_ast(AST_Node_Ptr node, std::string ind, Env* env) debug_ast(block->value(), ind + " value) "); } else if (Cast(node)) { - Media_Query_Ptr block = 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]": " -") @@ -293,81 +510,101 @@ inline void debug_ast(AST_Node_Ptr node, std::string ind, Env* env) << std::endl; debug_ast(block->media_type(), ind + " "); for(const auto& i : block->elements()) { debug_ast(i, ind + " ", env); } - - } else if (Cast(node)) { - Media_Block_Ptr block = Cast(node); - std::cerr << ind << "Media_Block " << block; - std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << " " << block->tabs() << std::endl; - debug_ast(block->media_queries(), ind + " =@ "); - if (block->block()) for(const Statement_Obj& i : block->block()->elements()) { debug_ast(i, ind + " ", env); } + } + else if (Cast(node)) { + MediaRule* rule = Cast(node); + std::cerr << ind << "MediaRule " << rule; + std::cerr << " (" << pstate_source_position(rule) << ")"; + std::cerr << " " << rule->tabs() << std::endl; + debug_ast(rule->schema(), ind + " =@ "); + debug_ast(rule->block(), ind + " "); + } + else if (Cast(node)) { + CssMediaRule* rule = Cast(node); + std::cerr << ind << "CssMediaRule " << rule; + std::cerr << " (" << pstate_source_position(rule) << ")"; + std::cerr << " " << rule->tabs() << std::endl; + for (auto item : rule->elements()) { + debug_ast(item, ind + " == "); + } + debug_ast(rule->block(), ind + " "); + } + else if (Cast(node)) { + CssMediaQuery* query = Cast(node); + std::cerr << ind << "CssMediaQuery " << query; + std::cerr << " (" << pstate_source_position(query) << ")"; + std::cerr << " [" << (query->modifier()) << "] "; + std::cerr << " [" << (query->type()) << "] "; + std::cerr << " " << debug_vec(query->features()); + std::cerr << std::endl; } else if (Cast(node)) { - Supports_Block_Ptr block = Cast(node); + Supports_Block* block = Cast(node); std::cerr << ind << "Supports_Block " << 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)) { - Supports_Operator_Ptr block = Cast(node); + Supports_Operator* block = Cast(node); std::cerr << ind << "Supports_Operator " << block; std::cerr << " (" << pstate_source_position(node) << ")" << std::endl; debug_ast(block->left(), ind + " left) "); debug_ast(block->right(), ind + " right) "); } else if (Cast(node)) { - Supports_Negation_Ptr block = Cast(node); + Supports_Negation* block = Cast(node); std::cerr << ind << "Supports_Negation " << block; std::cerr << " (" << pstate_source_position(node) << ")" << std::endl; debug_ast(block->condition(), ind + " condition) "); } else if (Cast(node)) { - At_Root_Query_Ptr block = 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)) { - Supports_Declaration_Ptr block = Cast(node); + Supports_Declaration* block = Cast(node); std::cerr << ind << "Supports_Declaration " << 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)) { - Block_Ptr root_block = 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)) { - Warning_Ptr block = Cast(node); + Warning* block = Cast(node); std::cerr << ind << "Warning " << block; std::cerr << " (" << pstate_source_position(node) << ")"; std::cerr << " " << block->tabs() << std::endl; debug_ast(block->message(), ind + " : "); } else if (Cast(node)) { - Error_Ptr block = Cast(node); + Error* block = Cast(node); std::cerr << ind << "Error " << block; std::cerr << " (" << pstate_source_position(node) << ")"; std::cerr << " " << block->tabs() << std::endl; } else if (Cast(node)) { - Debug_Ptr block = Cast(node); + Debug* block = Cast(node); std::cerr << ind << "Debug " << block; std::cerr << " (" << pstate_source_position(node) << ")"; std::cerr << " " << block->tabs() << std::endl; debug_ast(block->value(), ind + " "); } else if (Cast(node)) { - Comment_Ptr block = Cast(node); + Comment* block = Cast(node); std::cerr << ind << "Comment " << block; std::cerr << " (" << pstate_source_position(node) << ")"; std::cerr << " " << block->tabs() << " <" << prettyprint(block->pstate().token.ws_before()) << ">" << std::endl; debug_ast(block->text(), ind + "// ", env); } else if (Cast(node)) { - If_Ptr block = Cast(node); + If* block = Cast(node); std::cerr << ind << "If " << block; std::cerr << " (" << pstate_source_position(node) << ")"; std::cerr << " " << block->tabs() << std::endl; @@ -375,30 +612,31 @@ inline void debug_ast(AST_Node_Ptr node, std::string ind, Env* env) debug_ast(block->block(), ind + " <>"); debug_ast(block->alternative(), ind + " ><"); } else if (Cast(node)) { - Return_Ptr block = Cast(node); + Return* block = Cast(node); std::cerr << ind << "Return " << block; std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << " " << block->tabs() << std::endl; - } else if (Cast(node)) { - Extension_Ptr block = Cast(node); - std::cerr << ind << "Extension " << block; + std::cerr << " " << block->tabs(); + std::cerr << " [" << block->value()->to_string() << "]" << std::endl; + } else if (Cast(node)) { + ExtendRule* block = Cast(node); + std::cerr << ind << "ExtendRule " << block; std::cerr << " (" << pstate_source_position(node) << ")"; std::cerr << " " << block->tabs() << std::endl; debug_ast(block->selector(), ind + "-> ", env); } else if (Cast(node)) { - Content_Ptr block = Cast(node); + Content* block = Cast(node); std::cerr << ind << "Content " << block; std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << " [@media:" << block->media_block() << "]"; std::cerr << " " << block->tabs() << std::endl; + debug_ast(block->arguments(), ind + " args: ", env); } else if (Cast(node)) { - Import_Stub_Ptr block = Cast(node); + Import_Stub* block = Cast(node); std::cerr << ind << "Import_Stub " << block; std::cerr << " (" << pstate_source_position(node) << ")"; std::cerr << " [" << block->imp_path() << "] "; std::cerr << " " << block->tabs() << std::endl; } else if (Cast(node)) { - Import_Ptr block = Cast(node); + Import* block = Cast(node); std::cerr << ind << "Import " << block; std::cerr << " (" << pstate_source_position(node) << ")"; std::cerr << " " << block->tabs() << std::endl; @@ -406,13 +644,13 @@ inline void debug_ast(AST_Node_Ptr node, std::string ind, Env* env) for (auto imp : block->urls()) debug_ast(imp, ind + "@: ", env); debug_ast(block->import_queries(), ind + "@@ "); } else if (Cast(node)) { - Assignment_Ptr block = 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)) { - Declaration_Ptr block = 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() << "] "; @@ -421,14 +659,14 @@ inline void debug_ast(AST_Node_Ptr node, std::string ind, Env* env) debug_ast(block->value(), ind + " value: ", env); debug_ast(block->block(), ind + " ", env); } else if (Cast(node)) { - Keyframe_Rule_Ptr has_block = Cast(node); + Keyframe_Rule* has_block = Cast(node); std::cerr << ind << "Keyframe_Rule " << has_block; std::cerr << " (" << pstate_source_position(node) << ")"; std::cerr << " " << has_block->tabs() << std::endl; if (has_block->name()) debug_ast(has_block->name(), ind + "@"); if (has_block->block()) for(const Statement_Obj& i : has_block->block()->elements()) { debug_ast(i, ind + " ", env); } } else if (Cast(node)) { - Directive_Ptr block = Cast(node); + Directive* block = Cast(node); std::cerr << ind << "Directive " << block; std::cerr << " (" << pstate_source_position(node) << ")"; std::cerr << " [" << block->keyword() << "] " << block->tabs() << std::endl; @@ -436,25 +674,25 @@ inline void debug_ast(AST_Node_Ptr node, std::string ind, Env* 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)) { - Each_Ptr block = Cast(node); + Each* block = Cast(node); std::cerr << ind << "Each " << 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_Ptr block = Cast(node); + For* block = Cast(node); std::cerr << ind << "For " << 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)) { - While_Ptr block = Cast(node); + While* block = Cast(node); std::cerr << ind << "While " << 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_Ptr block = Cast(node); + Definition* block = Cast(node); std::cerr << ind << "Definition " << block; std::cerr << " (" << pstate_source_position(node) << ")"; std::cerr << " [name: " << block->name() << "] "; @@ -466,14 +704,15 @@ inline void debug_ast(AST_Node_Ptr node, std::string ind, Env* env) debug_ast(block->parameters(), ind + " params: ", env); if (block->block()) debug_ast(block->block(), ind + " ", env); } else if (Cast(node)) { - Mixin_Call_Ptr block = 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: "); + 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 (Ruleset_Ptr ruleset = Cast(node)) { + } else if (Ruleset* ruleset = Cast(node)) { std::cerr << ind << "Ruleset " << ruleset; std::cerr << " (" << pstate_source_position(node) << ")"; std::cerr << " [indent: " << ruleset->tabs() << "]"; @@ -483,30 +722,22 @@ inline void debug_ast(AST_Node_Ptr node, std::string ind, Env* env) debug_ast(ruleset->selector(), ind + ">"); debug_ast(ruleset->block(), ind + " "); } else if (Cast(node)) { - Block_Ptr block = 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_Ptr expression = 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; std::string name(expression->name()); if (env && env->has(name)) debug_ast(Cast((*env)[name]), ind + " -> ", env); - } else if (Cast(node)) { - Function_Call_Schema_Ptr expression = Cast(node); - std::cerr << ind << "Function_Call_Schema " << expression; - std::cerr << " [interpolant: " << expression->is_interpolant() << "] "; - std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << "" << std::endl; - debug_ast(expression->name(), ind + "name: ", env); - debug_ast(expression->arguments(), ind + " args: ", env); } else if (Cast(node)) { - Function_Call_Ptr expression = 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) << ")"; @@ -518,14 +749,14 @@ inline void debug_ast(AST_Node_Ptr node, std::string ind, Env* env) debug_ast(expression->arguments(), ind + " args: ", env); debug_ast(expression->func(), ind + " func: ", env); } else if (Cast(node)) { - Function_Ptr expression = 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_Ptr expression = Cast(node); + Arguments* expression = Cast(node); std::cerr << ind << "Arguments " << expression; if (expression->is_delayed()) std::cerr << " [delayed]"; std::cerr << " (" << pstate_source_position(node) << ")"; @@ -535,7 +766,7 @@ inline void debug_ast(AST_Node_Ptr node, std::string ind, Env* env) std::cerr << std::endl; for(const Argument_Obj& i : expression->elements()) { debug_ast(i, ind + " ", env); } } else if (Cast(node)) { - Argument_Ptr expression = Cast(node); + Argument* expression = Cast(node); std::cerr << ind << "Argument " << expression; std::cerr << " (" << pstate_source_position(node) << ")"; std::cerr << " [" << expression->value().ptr() << "]"; @@ -544,7 +775,7 @@ inline void debug_ast(AST_Node_Ptr node, std::string ind, Env* env) std::cerr << " [keyword: " << expression->is_keyword_argument() << "] " << std::endl; debug_ast(expression->value(), ind + " value: ", env); } else if (Cast(node)) { - Parameters_Ptr expression = 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() << "] "; @@ -552,14 +783,14 @@ inline void debug_ast(AST_Node_Ptr node, std::string ind, Env* env) std::cerr << std::endl; for(const Parameter_Obj& i : expression->elements()) { debug_ast(i, ind + " ", env); } } else if (Cast(node)) { - Parameter_Ptr expression = 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_Ptr expression = 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() << "] "; @@ -567,7 +798,7 @@ inline void debug_ast(AST_Node_Ptr node, std::string ind, Env* env) std::cerr << " [" << expression->type() << "]" << std::endl; debug_ast(expression->operand(), ind + " operand: ", env); } else if (Cast(node)) { - Binary_Expression_Ptr expression = 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] "; @@ -580,7 +811,7 @@ inline void debug_ast(AST_Node_Ptr node, std::string ind, Env* env) debug_ast(expression->left(), ind + " left: ", env); debug_ast(expression->right(), ind + " right: ", env); } else if (Cast(node)) { - Map_Ptr expression = Cast(node); + Map* expression = Cast(node); std::cerr << ind << "Map " << expression; std::cerr << " [interpolant: " << expression->is_interpolant() << "] "; std::cerr << " (" << pstate_source_position(node) << ")"; @@ -590,7 +821,7 @@ inline void debug_ast(AST_Node_Ptr node, std::string ind, Env* env) debug_ast(i.second, ind + " val: "); } } else if (Cast(node)) { - List_Ptr expression = Cast(node); + List* expression = Cast(node); std::cerr << ind << "List " << expression; std::cerr << " (" << pstate_source_position(node) << ")"; std::cerr << " (" << expression->length() << ") " << @@ -604,27 +835,30 @@ inline void debug_ast(AST_Node_Ptr node, std::string ind, Env* env) " [hash: " << expression->hash() << "] " << std::endl; for(const auto& i : expression->elements()) { debug_ast(i, ind + " ", env); } - } else if (Cast(node)) { - Content_Ptr expression = Cast(node); - std::cerr << ind << "Content " << expression; - std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << " [@media:" << expression->media_block() << "]"; - std::cerr << " [Statement]" << std::endl; } else if (Cast(node)) { - Boolean_Ptr expression = 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_Ptr expression = Cast(node); + } else if (Cast(node)) { + Color_RGBA* 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 << " [" << expression->r() << ":" << expression->g() << ":" << expression->b() << "@" << expression->a() << "]" << std::endl; + std::cerr << " rgba[" << expression->r() << ":" << expression->g() << ":" << expression->b() << "@" << expression->a() << "]" << std::endl; + } else if (Cast(node)) { + Color_HSLA* 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)) { - Number_Ptr expression = Cast(node); + Number* expression = Cast(node); std::cerr << ind << "Number " << expression; std::cerr << " (" << pstate_source_position(node) << ")"; std::cerr << " [delayed: " << expression->is_delayed() << "] "; @@ -633,14 +867,14 @@ inline void debug_ast(AST_Node_Ptr node, std::string ind, Env* env) " [hash: " << expression->hash() << "] " << std::endl; } else if (Cast(node)) { - Null_Ptr expression = Cast(node); + Null* expression = Cast(node); std::cerr << ind << "Null " << expression; std::cerr << " (" << pstate_source_position(node) << ")"; std::cerr << " [interpolant: " << expression->is_interpolant() << "] " // " [hash: " << expression->hash() << "] " << std::endl; } else if (Cast(node)) { - String_Quoted_Ptr expression = Cast(node); + String_Quoted* expression = Cast(node); std::cerr << ind << "String_Quoted " << expression; std::cerr << " (" << pstate_source_position(node) << ")"; std::cerr << " [" << prettyprint(expression->value()) << "]"; @@ -649,7 +883,7 @@ inline void debug_ast(AST_Node_Ptr node, std::string ind, Env* env) if (expression->quote_mark()) std::cerr << " [quote_mark: " << expression->quote_mark() << "]"; std::cerr << " <" << prettyprint(expression->pstate().token.ws_before()) << ">" << std::endl; } else if (Cast(node)) { - String_Constant_Ptr expression = Cast(node); + String_Constant* expression = Cast(node); std::cerr << ind << "String_Constant " << expression; if (expression->concrete_type()) { std::cerr << " " << expression->concrete_type(); @@ -660,7 +894,7 @@ inline void debug_ast(AST_Node_Ptr node, std::string ind, Env* env) if (expression->is_interpolant()) std::cerr << " [interpolant]"; std::cerr << " <" << prettyprint(expression->pstate().token.ws_before()) << ">" << std::endl; } else if (Cast(node)) { - String_Schema_Ptr expression = Cast(node); + String_Schema* expression = Cast(node); std::cerr << ind << "String_Schema " << expression; std::cerr << " (" << pstate_source_position(expression) << ")"; std::cerr << " " << expression->concrete_type(); @@ -674,42 +908,43 @@ inline void debug_ast(AST_Node_Ptr node, std::string ind, Env* env) std::cerr << " <" << prettyprint(expression->pstate().token.ws_before()) << ">" << std::endl; for(const auto& i : expression->elements()) { debug_ast(i, ind + " ", env); } } else if (Cast(node)) { - String_Ptr expression = Cast(node); + String* expression = Cast(node); std::cerr << ind << "String " << expression; std::cerr << " " << expression->concrete_type(); std::cerr << " (" << pstate_source_position(node) << ")"; if (expression->is_interpolant()) std::cerr << " [interpolant]"; std::cerr << " <" << prettyprint(expression->pstate().token.ws_before()) << ">" << std::endl; } else if (Cast(node)) { - Expression_Ptr expression = Cast(node); + Expression* expression = Cast(node); std::cerr << ind << "Expression " << expression; std::cerr << " (" << pstate_source_position(node) << ")"; switch (expression->concrete_type()) { - case Expression::Concrete_Type::NONE: std::cerr << " [NONE]"; break; - case Expression::Concrete_Type::BOOLEAN: std::cerr << " [BOOLEAN]"; break; - case Expression::Concrete_Type::NUMBER: std::cerr << " [NUMBER]"; break; - case Expression::Concrete_Type::COLOR: std::cerr << " [COLOR]"; break; - case Expression::Concrete_Type::STRING: std::cerr << " [STRING]"; break; - case Expression::Concrete_Type::LIST: std::cerr << " [LIST]"; break; - case Expression::Concrete_Type::MAP: std::cerr << " [MAP]"; break; - case Expression::Concrete_Type::SELECTOR: std::cerr << " [SELECTOR]"; break; - case Expression::Concrete_Type::NULL_VAL: std::cerr << " [NULL_VAL]"; break; - case Expression::Concrete_Type::C_WARNING: std::cerr << " [C_WARNING]"; break; - case Expression::Concrete_Type::C_ERROR: std::cerr << " [C_ERROR]"; break; - case Expression::Concrete_Type::FUNCTION: std::cerr << " [FUNCTION]"; break; - case Expression::Concrete_Type::NUM_TYPES: std::cerr << " [NUM_TYPES]"; break; - case Expression::Concrete_Type::VARIABLE: std::cerr << " [VARIABLE]"; break; - case Expression::Concrete_Type::FUNCTION_VAL: std::cerr << " [FUNCTION_VAL]"; break; + 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 << std::endl; } else if (Cast(node)) { - Has_Block_Ptr has_block = Cast(node); + Has_Block* has_block = Cast(node); std::cerr << ind << "Has_Block " << has_block; std::cerr << " (" << pstate_source_position(node) << ")"; std::cerr << " " << has_block->tabs() << std::endl; if (has_block->block()) for(const Statement_Obj& i : has_block->block()->elements()) { debug_ast(i, ind + " ", env); } } else if (Cast(node)) { - Statement_Ptr statement = Cast(node); + Statement* statement = Cast(node); std::cerr << ind << "Statement " << statement; std::cerr << " (" << pstate_source_position(node) << ")"; std::cerr << " " << statement->tabs() << std::endl; @@ -718,84 +953,12 @@ inline void debug_ast(AST_Node_Ptr node, std::string ind, Env* env) if (ind == "") std::cerr << "####################################################################\n"; } -inline void debug_node(Node* node, std::string ind = "") -{ - if (ind == "") std::cerr << "#@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@\n"; - if (node->isCombinator()) { - std::cerr << ind; - std::cerr << "Combinator "; - std::cerr << node << " "; - if (node->got_line_feed) std::cerr << "[LF] "; - switch (node->combinator()) { - case Complex_Selector::ADJACENT_TO: std::cerr << "{+} "; break; - case Complex_Selector::PARENT_OF: std::cerr << "{>} "; break; - case Complex_Selector::PRECEDES: std::cerr << "{~} "; break; - case Complex_Selector::REFERENCE: std::cerr << "{@} "; break; - case Complex_Selector::ANCESTOR_OF: std::cerr << "{ } "; break; - } - std::cerr << std::endl; - // debug_ast(node->combinator(), ind + " "); - } else if (node->isSelector()) { - std::cerr << ind; - std::cerr << "Selector "; - std::cerr << node << " "; - if (node->got_line_feed) std::cerr << "[LF] "; - std::cerr << std::endl; - debug_ast(node->selector(), ind + " "); - } else if (node->isCollection()) { - std::cerr << ind; - std::cerr << "Collection "; - std::cerr << node << " "; - if (node->got_line_feed) std::cerr << "[LF] "; - std::cerr << std::endl; - for(auto n : (*node->collection())) { - debug_node(&n, ind + " "); - } - } else if (node->isNil()) { - std::cerr << ind; - std::cerr << "Nil "; - std::cerr << node << " "; - if (node->got_line_feed) std::cerr << "[LF] "; - std::cerr << std::endl; - } else { - std::cerr << ind; - std::cerr << "OTHER "; - std::cerr << node << " "; - if (node->got_line_feed) std::cerr << "[LF] "; - std::cerr << std::endl; - } - if (ind == "") std::cerr << "#@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@\n"; -} /* -inline void debug_ast(const AST_Node_Ptr node, std::string ind = "", Env* env = 0) +inline void debug_ast(const AST_Node* node, std::string ind = "", Env* env = 0) { - debug_ast(const_cast(node), ind, env); + debug_ast(const_cast(node), ind, env); } */ -inline void debug_node(const Node* node, std::string ind = "") -{ - debug_node(const_cast(node), ind); -} - -inline void debug_subset_map(Sass::Subset_Map& map, std::string ind = "") -{ - if (ind == "") std::cerr << "#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n"; - for(auto const &it : map.values()) { - debug_ast(it.first, ind + "first: "); - debug_ast(it.second, ind + "second: "); - } - if (ind == "") std::cerr << "#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n"; -} - -inline void debug_subset_entries(SubSetMapPairs* entries, std::string ind = "") -{ - if (ind == "") std::cerr << "#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n"; - for(auto const &pair : *entries) { - debug_ast(pair.first, ind + "first: "); - debug_ast(pair.second, ind + "second: "); - } - if (ind == "") std::cerr << "#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n"; -} #endif // SASS_DEBUGGER diff --git a/src/libsass/src/emitter.cpp b/src/libsass/src/emitter.cpp index 161e689a9..27dcd9b6e 100644 --- a/src/libsass/src/emitter.cpp +++ b/src/libsass/src/emitter.cpp @@ -1,9 +1,9 @@ +// sass.hpp must go before all system headers to get the +// __EXTENSIONS__ fix on Solaris. #include "sass.hpp" -#include "util.hpp" -#include "context.hpp" -#include "output.hpp" #include "emitter.hpp" -#include "utf8_string.hpp" +#include "util_string.hpp" +#include "util.hpp" namespace Sass { @@ -47,11 +47,11 @@ namespace Sass { void Emitter::set_filename(const std::string& str) { wbuf.smap.file = str; } - void Emitter::schedule_mapping(const AST_Node_Ptr node) + void Emitter::schedule_mapping(const AST_Node* node) { scheduled_mapping = node; } - void Emitter::add_open_mapping(const AST_Node_Ptr node) + void Emitter::add_open_mapping(const AST_Node* node) { wbuf.smap.add_open_mapping(node); } - void Emitter::add_close_mapping(const AST_Node_Ptr node) + void Emitter::add_close_mapping(const AST_Node* node) { wbuf.smap.add_close_mapping(node); } ParserState Emitter::remap(const ParserState& pstate) { return wbuf.smap.remap(pstate); } @@ -134,13 +134,13 @@ namespace Sass { // write space/lf flush_schedules(); - if (in_comment && output_style() == COMPACT) { - // unescape comment nodes - std::string out = comment_to_string(text); - // add to buffer - wbuf.buffer += out; - // account for data in source-maps + if (in_comment) { + std::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; @@ -161,7 +161,7 @@ namespace Sass { // append some text or token to the buffer // this adds source-mappings for node start and end - void Emitter::append_token(const std::string& text, const AST_Node_Ptr node) + void Emitter::append_token(const std::string& text, const AST_Node* node) { flush_schedules(); add_open_mapping(node); @@ -263,7 +263,7 @@ namespace Sass { } } - void Emitter::append_scope_opener(AST_Node_Ptr node) + void Emitter::append_scope_opener(AST_Node* node) { scheduled_linefeed = 0; append_optional_space(); @@ -274,7 +274,7 @@ namespace Sass { // append_optional_space(); ++ indentation; } - void Emitter::append_scope_closer(AST_Node_Ptr node) + void Emitter::append_scope_closer(AST_Node* node) { -- indentation; scheduled_linefeed = 0; diff --git a/src/libsass/src/emitter.hpp b/src/libsass/src/emitter.hpp index 3bf8f60da..ceb4df584 100644 --- a/src/libsass/src/emitter.hpp +++ b/src/libsass/src/emitter.hpp @@ -1,8 +1,10 @@ #ifndef SASS_EMITTER_H #define SASS_EMITTER_H -#include +// sass.hpp must go before all system headers to get the +// __EXTENSIONS__ fix on Solaris. #include "sass.hpp" + #include "sass/base.h" #include "source_map.hpp" #include "ast_fwd_decl.hpp" @@ -25,9 +27,9 @@ namespace Sass { // proxy methods for source maps void add_source_index(size_t idx); void set_filename(const std::string& str); - void add_open_mapping(const AST_Node_Ptr node); - void add_close_mapping(const AST_Node_Ptr node); - void schedule_mapping(const AST_Node_Ptr node); + void add_open_mapping(const AST_Node* node); + void add_close_mapping(const AST_Node* node); + void schedule_mapping(const AST_Node* node); std::string render_srcmap(Context &ctx); ParserState remap(const ParserState& pstate); @@ -37,8 +39,8 @@ namespace Sass { size_t scheduled_space; size_t scheduled_linefeed; bool scheduled_delimiter; - AST_Node_Ptr scheduled_crutch; - AST_Node_Ptr scheduled_mapping; + const AST_Node* scheduled_crutch; + const AST_Node* scheduled_mapping; public: // output strings different in custom css properties @@ -75,7 +77,7 @@ namespace Sass { void append_wspace(const std::string& text); // append some text or token to the buffer // this adds source-mappings for node start and end - void append_token(const std::string& text, const AST_Node_Ptr node); + void append_token(const std::string& text, const AST_Node* node); // query last appended character char last_char(); @@ -86,8 +88,8 @@ namespace Sass { void append_special_linefeed(void); void append_optional_linefeed(void); void append_mandatory_linefeed(void); - void append_scope_opener(AST_Node_Ptr node = 0); - void append_scope_closer(AST_Node_Ptr node = 0); + void append_scope_opener(AST_Node* node = 0); + void append_scope_closer(AST_Node* node = 0); void append_comma_separator(void); void append_colon_separator(void); void append_delimiter(void); diff --git a/src/libsass/src/environment.cpp b/src/libsass/src/environment.cpp index e382e7e05..a3aecb30d 100644 --- a/src/libsass/src/environment.cpp +++ b/src/libsass/src/environment.cpp @@ -206,6 +206,20 @@ namespace Sass { } }; + // use array access for getter and setter functions + template + T& Environment::get(const std::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 std::string& key) @@ -230,7 +244,7 @@ namespace Sass { 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 << std::string(indent, ' ') << i->first << " " << i->second; - if (Value_Ptr val = Cast(i->second)) + if (Value* val = Cast(i->second)) { std::cerr << " : " << val->to_string(); } std::cerr << std::endl; } diff --git a/src/libsass/src/environment.hpp b/src/libsass/src/environment.hpp index a6939be23..606388657 100644 --- a/src/libsass/src/environment.hpp +++ b/src/libsass/src/environment.hpp @@ -1,12 +1,18 @@ #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 { @@ -92,6 +98,10 @@ namespace Sass { // include all scopes available bool has(const std::string& key) const; + // look on the full stack for key + // include all scopes available + T& get(const std::string& key); + // look on the full stack for key // include all scopes available EnvResult find(const std::string& key); @@ -107,6 +117,7 @@ namespace Sass { // define typedef for our use case typedef Environment Env; + typedef std::vector EnvStack; } diff --git a/src/libsass/src/error_handling.cpp b/src/libsass/src/error_handling.cpp index 745f65508..7aa5df31c 100644 --- a/src/libsass/src/error_handling.cpp +++ b/src/libsass/src/error_handling.cpp @@ -1,4 +1,7 @@ +// 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" @@ -15,43 +18,38 @@ namespace Sass { prefix("Error"), pstate(pstate), traces(traces) { } - InvalidSass::InvalidSass(ParserState pstate, Backtraces traces, std::string msg) - : Base(pstate, msg, traces) + InvalidSass::InvalidSass(ParserState pstate, Backtraces traces, std::string msg, char* owned_src) + : Base(pstate, msg, traces), owned_src(owned_src) { } - InvalidParent::InvalidParent(Selector_Ptr parent, Backtraces traces, Selector_Ptr selector) + InvalidParent::InvalidParent(Selector* parent, Backtraces traces, Selector* selector) : Base(selector->pstate(), def_msg, traces), parent(parent), selector(selector) { - msg = "Invalid parent selector for \""; - msg += selector->to_string(Sass_Inspect_Options()); - msg += "\": \""; - msg += parent->to_string(Sass_Inspect_Options()); - msg += "\""; + msg = "Invalid parent selector for " + "\"" + selector->to_string(Sass_Inspect_Options()) + "\": " + "\"" + parent->to_string(Sass_Inspect_Options()) + "\""; } - InvalidVarKwdType::InvalidVarKwdType(ParserState pstate, Backtraces traces, std::string name, const Argument_Ptr arg) + InvalidVarKwdType::InvalidVarKwdType(ParserState pstate, Backtraces traces, std::string name, const Argument* arg) : Base(pstate, def_msg, traces), name(name), arg(arg) { - msg = "Variable keyword argument map must have string keys.\n"; - msg += name + " is not a string in " + arg->to_string() + "."; + msg = "Variable keyword argument map must have string keys.\n" + + name + " is not a string in " + arg->to_string() + "."; } - InvalidArgumentType::InvalidArgumentType(ParserState pstate, Backtraces traces, std::string fn, std::string arg, std::string type, const Value_Ptr value) + InvalidArgumentType::InvalidArgumentType(ParserState pstate, Backtraces traces, std::string fn, std::string arg, std::string type, const Value* value) : Base(pstate, def_msg, traces), fn(fn), arg(arg), type(type), value(value) { - msg = arg + ": \""; + msg = arg + ": \""; if (value) msg += value->to_string(Sass_Inspect_Options()); - msg += "\" is not a " + type; - msg += " for `" + fn + "'"; + msg += "\" is not a " + type + " for `" + fn + "'"; } MissingArgument::MissingArgument(ParserState pstate, Backtraces traces, std::string fn, std::string arg, std::string fntype) : Base(pstate, def_msg, traces), fn(fn), arg(arg), fntype(fntype) { - msg = fntype + " " + fn; - msg += " is missing argument "; - msg += arg + "."; + msg = fntype + " " + fn + " is missing argument " + arg + "."; } InvalidSyntax::InvalidSyntax(ParserState pstate, Backtraces traces, std::string msg) @@ -65,87 +63,66 @@ namespace Sass { DuplicateKeyError::DuplicateKeyError(Backtraces traces, const Map& dup, const Expression& org) : Base(org.pstate(), def_msg, traces), dup(dup), org(org) { - msg = "Duplicate key "; - msg += dup.get_duplicate_key()->inspect(); - msg += " in map ("; - msg += org.inspect(); - msg += ")."; + msg = "Duplicate key " + dup.get_duplicate_key()->inspect() + " in map (" + org.inspect() + ")."; } TypeMismatch::TypeMismatch(Backtraces traces, const Expression& var, const std::string type) : Base(var.pstate(), def_msg, traces), var(var), type(type) { - msg = var.to_string(); - msg += " is not an "; - msg += type; - msg += "."; + msg = var.to_string() + " is not an " + type + "."; } InvalidValue::InvalidValue(Backtraces traces, const Expression& val) : Base(val.pstate(), def_msg, traces), val(val) { - msg = val.to_string(); - msg += " isn't a valid CSS value."; + msg = val.to_string() + " isn't a valid CSS value."; } StackError::StackError(Backtraces traces, const AST_Node& node) : Base(node.pstate(), def_msg, traces), node(node) { - msg = "stack level too deep"; + msg = "stack level too deep"; } IncompatibleUnits::IncompatibleUnits(const Units& lhs, const Units& rhs) { - msg = "Incompatible units: '"; - msg += rhs.unit(); - msg += "' and '"; - msg += lhs.unit(); - msg += "'."; + msg = "Incompatible units: '" + rhs.unit() + "' and '" + lhs.unit() + "'."; } IncompatibleUnits::IncompatibleUnits(const UnitType lhs, const UnitType rhs) { - msg = "Incompatible units: '"; - msg += unit_to_string(rhs); - msg += "' and '"; - msg += unit_to_string(lhs); - msg += "'."; + msg = std::string("Incompatible units: '") + unit_to_string(rhs) + "' and '" + unit_to_string(lhs) + "'."; } - AlphaChannelsNotEqual::AlphaChannelsNotEqual(Expression_Ptr_Const lhs, Expression_Ptr_Const rhs, enum Sass_OP op) + 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: "; - msg += lhs->to_string({ NESTED, 5 }); - msg += " " + sass_op_to_name(op) + " "; - msg += rhs->to_string({ NESTED, 5 }); - msg += "."; + msg = "Alpha channels must be equal: " + + lhs->to_string({ NESTED, 5 }) + + " " + sass_op_to_name(op) + " " + + rhs->to_string({ NESTED, 5 }) + "."; } ZeroDivisionError::ZeroDivisionError(const Expression& lhs, const Expression& rhs) : OperationError(), lhs(lhs), rhs(rhs) { - msg = "divided by 0"; + msg = "divided by 0"; } - UndefinedOperation::UndefinedOperation(Expression_Ptr_Const lhs, Expression_Ptr_Const rhs, enum Sass_OP op) + UndefinedOperation::UndefinedOperation(const Expression* lhs, const Expression* rhs, enum Sass_OP op) : OperationError(), lhs(lhs), rhs(rhs), op(op) { - msg = def_op_msg + ": \""; - msg += lhs->to_string({ NESTED, 5 }); - msg += " " + sass_op_to_name(op) + " "; - msg += rhs->to_string({ TO_SASS, 5 }); - msg += "\"."; + msg = def_op_msg + ": \"" + + lhs->to_string({ NESTED, 5 }) + + " " + sass_op_to_name(op) + " " + + rhs->to_string({ TO_SASS, 5 }) + + "\"."; } - InvalidNullOperation::InvalidNullOperation(Expression_Ptr_Const lhs, Expression_Ptr_Const rhs, enum Sass_OP op) + InvalidNullOperation::InvalidNullOperation(const Expression* lhs, const Expression* rhs, enum Sass_OP op) : UndefinedOperation(lhs, rhs, op) { - msg = def_op_null_msg + ": \""; - msg += lhs->inspect(); - msg += " " + sass_op_to_name(op) + " "; - msg += rhs->inspect(); - msg += "\"."; + msg = def_op_null_msg + ": \"" + lhs->inspect() + " " + sass_op_to_name(op) + " " + rhs->inspect() + "\"."; } SassValueError::SassValueError(Backtraces traces, ParserState pstate, OperationError& err) @@ -155,6 +132,27 @@ namespace Sass { prefix = err.errtype(); } + TopLevelParent::TopLevelParent(Backtraces traces, ParserState pstate) + : Base(pstate, "Top-level selectors may not contain the parent selector \"&\".", traces) + { + + } + + UnsatisfiedExtend::UnsatisfiedExtend(Backtraces traces, Extension extension) + : Base(extension.target->pstate(), "The target selector was not found.\n" + "Use \"@extend " + extension.target->to_string() + " !optional\" to avoid this error.", traces) + { + + } + + ExtendAcrossMedia::ExtendAcrossMedia(Backtraces traces, Extension extension) + : Base(extension.target->pstate(), "You may not @extend selectors across media queries.\n" + "Use \"@extend " + extension.target->to_string() + " !optional\" to avoid this error.", traces) + { + + } + + } diff --git a/src/libsass/src/error_handling.hpp b/src/libsass/src/error_handling.hpp index f863792ea..d5157d6dc 100644 --- a/src/libsass/src/error_handling.hpp +++ b/src/libsass/src/error_handling.hpp @@ -1,9 +1,14 @@ #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" @@ -36,16 +41,28 @@ namespace Sass { class InvalidSass : public Base { public: - InvalidSass(ParserState pstate, Backtraces traces, std::string msg); - virtual ~InvalidSass() throw() {}; + InvalidSass(InvalidSass& other) : Base(other), owned_src(other.owned_src) { + // Assumes that `this` will outlive `other`. + other.owned_src = nullptr; + } + + // Required because the copy constructor's argument is not const. + // Can't use `std::move` here because we build on Visual Studio 2013. + InvalidSass(InvalidSass &&other) : Base(other), owned_src(other.owned_src) { + other.owned_src = nullptr; + } + + InvalidSass(ParserState pstate, Backtraces traces, std::string msg, char* owned_src = nullptr); + virtual ~InvalidSass() throw() { sass_free_memory(owned_src); }; + char *owned_src; }; class InvalidParent : public Base { protected: - Selector_Ptr parent; - Selector_Ptr selector; + Selector* parent; + Selector* selector; public: - InvalidParent(Selector_Ptr parent, Backtraces traces, Selector_Ptr selector); + InvalidParent(Selector* parent, Backtraces traces, Selector* selector); virtual ~InvalidParent() throw() {}; }; @@ -64,18 +81,18 @@ namespace Sass { std::string fn; std::string arg; std::string type; - const Value_Ptr value; + const Value* value; public: - InvalidArgumentType(ParserState pstate, Backtraces traces, std::string fn, std::string arg, std::string type, const Value_Ptr value = 0); + InvalidArgumentType(ParserState pstate, Backtraces traces, std::string fn, std::string arg, std::string type, const Value* value = 0); virtual ~InvalidArgumentType() throw() {}; }; class InvalidVarKwdType : public Base { protected: std::string name; - const Argument_Ptr arg; + const Argument* arg; public: - InvalidVarKwdType(ParserState pstate, Backtraces traces, std::string name, const Argument_Ptr arg = 0); + InvalidVarKwdType(ParserState pstate, Backtraces traces, std::string name, const Argument* arg = 0); virtual ~InvalidVarKwdType() throw() {}; }; @@ -165,36 +182,54 @@ namespace Sass { class UndefinedOperation : public OperationError { protected: - Expression_Ptr_Const lhs; - Expression_Ptr_Const rhs; + const Expression* lhs; + const Expression* rhs; const Sass_OP op; public: - UndefinedOperation(Expression_Ptr_Const lhs, Expression_Ptr_Const rhs, enum Sass_OP op); + 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(Expression_Ptr_Const lhs, Expression_Ptr_Const rhs, enum Sass_OP op); + InvalidNullOperation(const Expression* lhs, const Expression* rhs, enum Sass_OP op); virtual ~InvalidNullOperation() throw() {}; }; class AlphaChannelsNotEqual : public OperationError { protected: - Expression_Ptr_Const lhs; - Expression_Ptr_Const rhs; + const Expression* lhs; + const Expression* rhs; const Sass_OP op; public: - AlphaChannelsNotEqual(Expression_Ptr_Const lhs, Expression_Ptr_Const rhs, enum Sass_OP op); + 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, ParserState pstate, OperationError& err); - virtual ~SassValueError() throw() {}; + public: + SassValueError(Backtraces traces, ParserState pstate, OperationError& err); + virtual ~SassValueError() throw() {}; + }; + + class TopLevelParent : public Base { + public: + TopLevelParent(Backtraces traces, ParserState pstate); + virtual ~TopLevelParent() throw() {}; + }; + + class UnsatisfiedExtend : public Base { + public: + UnsatisfiedExtend(Backtraces traces, Extension extension); + virtual ~UnsatisfiedExtend() throw() {}; + }; + + class ExtendAcrossMedia : public Base { + public: + ExtendAcrossMedia(Backtraces traces, Extension extension); + virtual ~ExtendAcrossMedia() throw() {}; }; } diff --git a/src/libsass/src/eval.cpp b/src/libsass/src/eval.cpp index 841f7277b..93bce0acc 100644 --- a/src/libsass/src/eval.cpp +++ b/src/libsass/src/eval.cpp @@ -1,4 +1,7 @@ +// sass.hpp must go before all system headers to get the +// __EXTENSIONS__ fix on Solaris. #include "sass.hpp" + #include #include #include @@ -17,7 +20,8 @@ #include "position.hpp" #include "sass/values.h" #include "to_value.hpp" -#include "to_c.hpp" +#include "ast2c.hpp" +#include "c2ast.hpp" #include "context.hpp" #include "backtrace.hpp" #include "lexer.hpp" @@ -26,6 +30,7 @@ #include "expand.hpp" #include "color_maps.hpp" #include "sass_functions.hpp" +#include "util_string.hpp" namespace Sass { @@ -47,14 +52,34 @@ namespace Sass { return exp.environment(); } - Selector_List_Obj Eval::selector() + const std::string Eval::cwd() + { + return ctx.cwd(); + } + + struct Sass_Inspect_Options& Eval::options() + { + return ctx.c_options; + } + + struct Sass_Compiler* Eval::compiler() + { + return ctx.c_compiler; + } + + EnvStack& Eval::env_stack() { - return exp.selector(); + return exp.env_stack; } - Expression_Ptr Eval::operator()(Block_Ptr b) + std::vector& Eval::callee_stack() { - Expression_Ptr val = 0; + return ctx.callee_stack; + } + + Expression* Eval::operator()(Block* b) + { + Expression* val = 0; for (size_t i = 0, L = b->length(); i < L; ++i) { val = b->at(i)->perform(this); if (val) return val; @@ -62,14 +87,14 @@ namespace Sass { return val; } - Expression_Ptr Eval::operator()(Assignment_Ptr a) + Expression* Eval::operator()(Assignment* a) { - Env* env = exp.environment(); + Env* env = environment(); std::string var(a->variable()); if (a->is_global()) { if (a->is_default()) { if (env->has_global(var)) { - Expression_Ptr e = Cast(env->get_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)); } @@ -88,7 +113,7 @@ namespace Sass { while (cur && cur->is_lexical()) { if (cur->has_local(var)) { if (AST_Node_Obj node = cur->get_local(var)) { - Expression_Ptr e = Cast(node); + Expression* e = Cast(node); if (!e || e->concrete_type() == Expression::NULL_VAL) { cur->set_local(var, a->value()->perform(this)); } @@ -104,7 +129,7 @@ namespace Sass { } else if (env->has_global(var)) { if (AST_Node_Obj node = env->get_global(var)) { - Expression_Ptr e = Cast(node); + Expression* e = Cast(node); if (!e || e->concrete_type() == Expression::NULL_VAL) { env->set_global(var, a->value()->perform(this)); } @@ -123,11 +148,11 @@ namespace Sass { return 0; } - Expression_Ptr Eval::operator()(If_Ptr i) + Expression* Eval::operator()(If* i) { - Expression_Obj rv = 0; - Env env(exp.environment()); - exp.env_stack.push_back(&env); + Expression_Obj rv; + Env env(environment()); + env_stack().push_back(&env); Expression_Obj cond = i->predicate()->perform(this); if (!cond->is_false()) { rv = i->block()->perform(this); @@ -136,13 +161,13 @@ namespace Sass { Block_Obj alt = i->alternative(); if (alt) rv = alt->perform(this); } - exp.env_stack.pop_back(); + env_stack().pop_back(); return rv.detach(); } // For does not create a new env scope // But iteration vars are reset afterwards - Expression_Ptr Eval::operator()(For_Ptr f) + Expression* Eval::operator()(For* f) { std::string variable(f->variable()); Expression_Obj low = f->lower_bound()->perform(this); @@ -168,9 +193,9 @@ namespace Sass { double end = sass_end->value(); // only create iterator once in this environment Env env(environment(), true); - exp.env_stack.push_back(&env); + env_stack().push_back(&env); Block_Obj body = f->block(); - Expression_Ptr val = 0; + Expression* val = 0; if (start < end) { if (f->is_inclusive()) ++end; for (double i = start; @@ -192,26 +217,25 @@ namespace Sass { if (val) break; } } - exp.env_stack.pop_back(); + env_stack().pop_back(); return val; } // Eval does not create a new env scope // But iteration vars are reset afterwards - Expression_Ptr Eval::operator()(Each_Ptr e) + Expression* Eval::operator()(Each* e) { std::vector variables(e->variables()); Expression_Obj expr = e->list()->perform(this); Env env(environment(), true); - exp.env_stack.push_back(&env); - List_Obj list = 0; - Map_Ptr map = 0; + env_stack().push_back(&env); + List_Obj list; + Map* map = nullptr; if (expr->concrete_type() == Expression::MAP) { map = Cast(expr); } - else if (Selector_List_Ptr ls = Cast(expr)) { - Listize listize; - Expression_Obj rv = ls->perform(&listize); + else if (SelectorList * ls = Cast(expr)) { + Expression_Obj rv = Listize::perform(ls); list = Cast(rv); } else if (expr->concrete_type() != Expression::LIST) { @@ -223,14 +247,14 @@ namespace Sass { } Block_Obj body = e->block(); - Expression_Obj val = 0; + Expression_Obj val; if (map) { for (Expression_Obj key : map->keys()) { Expression_Obj value = map->at(key); if (variables.size() == 1) { - List_Ptr variable = SASS_MEMORY_NEW(List, map->pstate(), 2, SASS_SPACE); + List* variable = SASS_MEMORY_NEW(List, map->pstate(), 2, SASS_SPACE); variable->append(key); variable->append(value); env.set_local(variables[0], variable); @@ -244,22 +268,22 @@ namespace Sass { } } else { - if (list->length() == 1 && Cast(list)) { + if (list->length() == 1 && Cast(list)) { list = Cast(list); } for (size_t i = 0, L = list->length(); i < L; ++i) { - Expression_Ptr item = list->at(i); + Expression* item = list->at(i); // unwrap value if the expression is an argument - if (Argument_Ptr arg = Cast(item)) item = arg->value(); + if (Argument* arg = Cast(item)) item = arg->value(); // check if we got passed a list of args (investigate) - if (List_Ptr scalars = Cast(item)) { + if (List* scalars = Cast(item)) { if (variables.size() == 1) { - Expression_Ptr var = scalars; + Expression* var = scalars; env.set_local(variables[0], var); } else { // XXX: this is never hit via spec tests for (size_t j = 0, K = variables.size(); j < K; ++j) { - Expression_Ptr res = j >= scalars->length() + Expression* res = j >= scalars->length() ? SASS_MEMORY_NEW(Null, expr->pstate()) : scalars->at(j); env.set_local(variables[j], res); @@ -270,7 +294,7 @@ namespace Sass { 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_Ptr res = SASS_MEMORY_NEW(Null, expr->pstate()); + Expression* res = SASS_MEMORY_NEW(Null, expr->pstate()); env.set_local(variables[j], res); } } @@ -279,46 +303,46 @@ namespace Sass { if (val) break; } } - exp.env_stack.pop_back(); + env_stack().pop_back(); return val.detach(); } - Expression_Ptr Eval::operator()(While_Ptr w) + Expression* Eval::operator()(While* w) { Expression_Obj pred = w->predicate(); Block_Obj body = w->block(); Env env(environment(), true); - exp.env_stack.push_back(&env); + env_stack().push_back(&env); Expression_Obj cond = pred->perform(this); while (!cond->is_false()) { Expression_Obj val = body->perform(this); if (val) { - exp.env_stack.pop_back(); + env_stack().pop_back(); return val.detach(); } cond = pred->perform(this); } - exp.env_stack.pop_back(); + env_stack().pop_back(); return 0; } - Expression_Ptr Eval::operator()(Return_Ptr r) + Expression* Eval::operator()(Return* r) { return r->value()->perform(this); } - Expression_Ptr Eval::operator()(Warning_Ptr w) + Expression* Eval::operator()(Warning* w) { - Sass_Output_Style outstyle = ctx.c_options.output_style; - ctx.c_options.output_style = NESTED; + Sass_Output_Style outstyle = options().output_style; + options().output_style = NESTED; Expression_Obj message = w->message()->perform(this); - Env* env = exp.environment(); + Env* env = environment(); // try to use generic function if (env->has("@warn[f]")) { // add call stack entry - ctx.callee_stack.push_back({ + callee_stack().push_back({ "@warn", w->pstate().path, w->pstate().line + 1, @@ -327,18 +351,18 @@ namespace Sass { { env } }); - Definition_Ptr def = Cast((*env)["@warn[f]"]); + 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); - To_C to_c; + AST2C ast2c; union Sass_Value* c_args = sass_make_list(1, SASS_COMMA, false); - sass_list_set_value(c_args, 0, message->perform(&to_c)); - union Sass_Value* c_val = c_func(c_args, c_function, ctx.c_compiler); - ctx.c_options.output_style = outstyle; - ctx.callee_stack.pop_back(); + 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; @@ -350,23 +374,23 @@ namespace Sass { traces.push_back(Backtrace(w->pstate())); std::cerr << traces_to_string(traces, " "); std::cerr << std::endl; - ctx.c_options.output_style = outstyle; + options().output_style = outstyle; traces.pop_back(); return 0; } - Expression_Ptr Eval::operator()(Error_Ptr e) + Expression* Eval::operator()(Error* e) { - Sass_Output_Style outstyle = ctx.c_options.output_style; - ctx.c_options.output_style = NESTED; + Sass_Output_Style outstyle = options().output_style; + options().output_style = NESTED; Expression_Obj message = e->message()->perform(this); - Env* env = exp.environment(); + Env* env = environment(); // try to use generic function if (env->has("@error[f]")) { // add call stack entry - ctx.callee_stack.push_back({ + callee_stack().push_back({ "@error", e->pstate().path, e->pstate().line + 1, @@ -375,18 +399,18 @@ namespace Sass { { env } }); - Definition_Ptr def = Cast((*env)["@error[f]"]); + 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); - To_C to_c; + AST2C ast2c; union Sass_Value* c_args = sass_make_list(1, SASS_COMMA, false); - sass_list_set_value(c_args, 0, message->perform(&to_c)); - union Sass_Value* c_val = c_func(c_args, c_function, ctx.c_compiler); - ctx.c_options.output_style = outstyle; - ctx.callee_stack.pop_back(); + 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; @@ -394,23 +418,23 @@ namespace Sass { } std::string result(unquote(message->to_sass())); - ctx.c_options.output_style = outstyle; + options().output_style = outstyle; error(result, e->pstate(), traces); return 0; } - Expression_Ptr Eval::operator()(Debug_Ptr d) + Expression* Eval::operator()(Debug* d) { - Sass_Output_Style outstyle = ctx.c_options.output_style; - ctx.c_options.output_style = NESTED; + Sass_Output_Style outstyle = options().output_style; + options().output_style = NESTED; Expression_Obj message = d->value()->perform(this); - Env* env = exp.environment(); + Env* env = environment(); // try to use generic function if (env->has("@debug[f]")) { // add call stack entry - ctx.callee_stack.push_back({ + callee_stack().push_back({ "@debug", d->pstate().path, d->pstate().line + 1, @@ -419,37 +443,37 @@ namespace Sass { { env } }); - Definition_Ptr def = Cast((*env)["@debug[f]"]); + 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); - To_C to_c; + AST2C ast2c; union Sass_Value* c_args = sass_make_list(1, SASS_COMMA, false); - sass_list_set_value(c_args, 0, message->perform(&to_c)); - union Sass_Value* c_val = c_func(c_args, c_function, ctx.c_compiler); - ctx.c_options.output_style = outstyle; - ctx.callee_stack.pop_back(); + 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; } - std::string cwd(ctx.cwd()); std::string result(unquote(message->to_sass())); - std::string abs_path(Sass::File::rel2abs(d->pstate().path, cwd, cwd)); - std::string rel_path(Sass::File::abs2rel(d->pstate().path, cwd, cwd)); + std::string abs_path(Sass::File::rel2abs(d->pstate().path, cwd(), cwd())); + std::string rel_path(Sass::File::abs2rel(d->pstate().path, cwd(), cwd())); std::string output_path(Sass::File::path_for_console(rel_path, abs_path, d->pstate().path)); - ctx.c_options.output_style = outstyle; + options().output_style = outstyle; std::cerr << output_path << ":" << d->pstate().line+1 << " DEBUG: " << result; std::cerr << std::endl; return 0; } - Expression_Ptr Eval::operator()(List_Ptr l) + + Expression* Eval::operator()(List* l) { // special case for unevaluated map if (l->separator() == SASS_HASH) { @@ -490,7 +514,7 @@ namespace Sass { return ll.detach(); } - Expression_Ptr Eval::operator()(Map_Ptr m) + Expression* Eval::operator()(Map* m) { if (m->is_expanded()) return m; @@ -505,8 +529,8 @@ namespace Sass { m->pstate(), m->length()); for (auto key : m->keys()) { - Expression_Ptr ex_key = key->perform(this); - Expression_Ptr ex_val = m->at(key); + 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); @@ -522,7 +546,7 @@ namespace Sass { return mm.detach(); } - Expression_Ptr Eval::operator()(Binary_Expression_Ptr b_in) + Expression* Eval::operator()(Binary_Expression* b_in) { Expression_Obj lhs = b_in->left(); @@ -543,21 +567,21 @@ namespace Sass { } // Evaluate variables as early o - while (Variable_Ptr l_v = Cast(lhs)) { + while (Variable* l_v = Cast(lhs)) { lhs = operator()(l_v); } - while (Variable_Ptr r_v = Cast(rhs)) { + while (Variable* r_v = Cast(rhs)) { rhs = operator()(r_v); } Binary_Expression_Obj b = b_in; // Evaluate sub-expressions early on - while (Binary_Expression_Ptr l_b = Cast(lhs)) { + while (Binary_Expression* l_b = Cast(lhs)) { if (!force && l_b->is_delayed()) break; lhs = operator()(l_b); } - while (Binary_Expression_Ptr r_b = Cast(rhs)) { + while (Binary_Expression* r_b = Cast(rhs)) { if (!force && r_b->is_delayed()) break; rhs = operator()(r_b); } @@ -571,9 +595,9 @@ namespace Sass { // specific types we know are final // handle them early to avoid overhead - if (Number_Ptr l_n = Cast(lhs)) { + if (Number* l_n = Cast(lhs)) { // lhs is number and rhs is number - if (Number_Ptr r_n = Cast(rhs)) { + if (Number* r_n = Cast(rhs)) { try { switch (op_type) { case Sass_OP::EQ: return *l_n == *r_n ? bool_true : bool_false; @@ -583,7 +607,7 @@ namespace Sass { 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, ctx.c_options, b_in->pstate()); + return Operators::op_numbers(op_type, *l_n, *r_n, options(), b_in->pstate()); default: break; } } @@ -594,13 +618,15 @@ namespace Sass { } } // lhs is number and rhs is color - else if (Color_Ptr r_c = Cast(rhs)) { + // 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, ctx.c_options, b_in->pstate()); + return Operators::op_number_color(op_type, *l_n, *r_c, options(), b_in->pstate()); default: break; } } @@ -611,9 +637,11 @@ namespace Sass { } } } - else if (Color_Ptr l_c = Cast(lhs)) { + else if (Color* l_col = Cast(lhs)) { + Color_RGBA_Obj l_c = l_col->toRGBA(); // lhs is color and rhs is color - if (Color_Ptr r_c = Cast(rhs)) { + 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; @@ -623,7 +651,7 @@ namespace Sass { 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, ctx.c_options, b_in->pstate()); + return Operators::op_colors(op_type, *l_c, *r_c, options(), b_in->pstate()); default: break; } } @@ -634,13 +662,13 @@ namespace Sass { } } // lhs is color and rhs is number - else if (Number_Ptr r_n = Cast(rhs)) { + 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, ctx.c_options, b_in->pstate()); + return Operators::op_color_number(op_type, *l_c, *r_n, options(), b_in->pstate()); default: break; } } @@ -655,7 +683,7 @@ namespace Sass { String_Schema_Obj ret_schema; // only the last item will be used to eval the binary expression - if (String_Schema_Ptr s_l = Cast(b->left())) { + 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_Expression_Obj bin_ex = SASS_MEMORY_NEW(Binary_Expression, b->pstate(), @@ -668,7 +696,7 @@ namespace Sass { return ret_schema->perform(this); } } - if (String_Schema_Ptr s_r = Cast(b->right())) { + if (String_Schema* s_r = Cast(b->right())) { if (!s_r->has_interpolant() && (!s_r->is_left_interpolant() || op_type == Sass_OP::DIV)) { ret_schema = SASS_MEMORY_NEW(String_Schema, b->pstate()); @@ -708,8 +736,8 @@ namespace Sass { AST_Node_Obj lu = lhs; AST_Node_Obj ru = rhs; - Expression::Concrete_Type l_type; - Expression::Concrete_Type r_type; + Expression::Type l_type; + Expression::Type r_type; // Is one of the operands an interpolant? String_Schema_Obj s1 = Cast(b->left()); @@ -729,7 +757,7 @@ namespace Sass { 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_Ptr str = Cast(lhs)) { + if (String_Constant* str = Cast(lhs)) { std::string value(str->value()); const char* start = value.c_str(); if (Prelexer::sequence < Prelexer::dimension, Prelexer::end_of_file >(start) != 0) { @@ -737,7 +765,7 @@ namespace Sass { } } // If possible upgrade RHS to a number (for string to number compare) - if (String_Constant_Ptr str = Cast(rhs)) { + if (String_Constant* str = Cast(rhs)) { std::string value(str->value()); const char* start = value.c_str(); if (Prelexer::sequence < Prelexer::dimension, Prelexer::number >(start) != 0) { @@ -752,12 +780,12 @@ namespace Sass { if (force_delay) { std::string str(""); - str += v_l->to_string(ctx.c_options); + 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(ctx.c_options); - String_Constant_Ptr val = SASS_MEMORY_NEW(String_Constant, b->pstate(), 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; } @@ -777,7 +805,6 @@ namespace Sass { } catch (Exception::OperationError& err) { - // throw Exception::Base(b->pstate(), err.what()); traces.push_back(Backtrace(b->pstate())); throw Exception::SassValueError(traces, b->pstate(), err); } @@ -791,25 +818,25 @@ namespace Sass { try { ParserState pstate(b->pstate()); if (l_type == Expression::NUMBER && r_type == Expression::NUMBER) { - Number_Ptr l_n = Cast(lhs); - Number_Ptr r_n = Cast(rhs); + 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, ctx.c_options, pstate); + rv = Operators::op_numbers(op_type, *l_n, *r_n, options(), pstate); } else if (l_type == Expression::NUMBER && r_type == Expression::COLOR) { - Number_Ptr l_n = Cast(lhs); - Color_Ptr r_c = Cast(rhs); - rv = Operators::op_number_color(op_type, *l_n, *r_c, ctx.c_options, pstate); + 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_Ptr l_c = Cast(lhs); - Number_Ptr r_n = Cast(rhs); - rv = Operators::op_color_number(op_type, *l_c, *r_n, ctx.c_options, pstate); + 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_Ptr l_c = Cast(lhs); - Color_Ptr r_c = Cast(rhs); - rv = Operators::op_colors(op_type, *l_c, *r_c, ctx.c_options, pstate); + 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); } else { To_Value to_value(ctx); @@ -830,15 +857,15 @@ namespace Sass { traces.push_back(Backtrace(v_r->pstate())); throw Exception::InvalidValue(traces, *v_r); } - Value_Ptr ex = Operators::op_strings(b->op(), *v_l, *v_r, ctx.c_options, pstate, !interpolant); // pass true to compress - if (String_Constant_Ptr str = Cast(ex)) + 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_Ptr lstr = Cast(lhs); - String_Constant_Ptr rstr = Cast(rhs); + String_Constant* lstr = Cast(lhs); + String_Constant* rstr = Cast(rhs); if (op_type != Sass_OP::SUB) { - if (String_Constant_Ptr org = lstr ? lstr : rstr) + if (String_Constant* org = lstr ? lstr : rstr) { str->quote_mark(org->quote_mark()); } } } @@ -866,11 +893,11 @@ namespace Sass { } - Expression_Ptr Eval::operator()(Unary_Expression_Ptr u) + Expression* Eval::operator()(Unary_Expression* u) { Expression_Obj operand = u->operand()->perform(this); if (u->optype() == Unary_Expression::NOT) { - Boolean_Ptr result = SASS_MEMORY_NEW(Boolean, u->pstate(), (bool)*operand); + Boolean* result = SASS_MEMORY_NEW(Boolean, u->pstate(), (bool)*operand); result->value(!result->value()); return result; } @@ -882,28 +909,35 @@ namespace Sass { return cpy.detach(); // return the copy } else if (u->optype() == Unary_Expression::SLASH) { - std::string str = '/' + nr->to_string(ctx.c_options); + std::string str = '/' + nr->to_string(options()); return SASS_MEMORY_NEW(String_Constant, u->pstate(), str); } // nothing for positive return nr.detach(); } else { - // Special cases: +/- variables which evaluate to null ouput just +/-, + // 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_Ptr color = Cast(operand)) { + else if (Color* color = Cast(operand)) { // Use the color name if this was eval with one if (color->disp().length() > 0) { - operand = SASS_MEMORY_NEW(String_Constant, operand->pstate(), color->disp()); - u->operand(operand); + Unary_Expression_Obj 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 { - u->operand(operand); + Unary_Expression_Obj cpy = SASS_MEMORY_COPY(u); + cpy->operand(operand); + return SASS_MEMORY_NEW(String_Quoted, + cpy->pstate(), + cpy->inspect()); } return SASS_MEMORY_NEW(String_Quoted, @@ -914,7 +948,7 @@ namespace Sass { return u; } - Expression_Ptr Eval::operator()(Function_Call_Ptr c) + Expression* Eval::operator()(Function_Call* c) { if (traces.size() > Constants::MaxCallStack) { // XXX: this is never hit via spec tests @@ -922,8 +956,18 @@ namespace Sass { stm << "Stack depth exceeded max of " << Constants::MaxCallStack; error(stm.str(), c->pstate(), traces); } + + if (Cast(c->sname())) { + Expression_Obj evaluated_name = c->sname()->perform(this); + Expression_Obj evaluated_args = c->arguments()->perform(this); + std::string str(evaluated_name->to_string()); + str += evaluated_args->to_string(); + return SASS_MEMORY_NEW(String_Constant, c->pstate(), str); + } + std::string name(Util::normalize_underscores(c->name())); std::string full_name(name + "[f]"); + // we make a clone here, need to implement that further Arguments_Obj args = c->arguments(); @@ -941,11 +985,11 @@ namespace Sass { c->name(), args); if (args->has_named_arguments()) { - error("Function " + c->name() + " doesn't support keyword arguments", c->pstate(), traces); + error("Plain CSS function " + c->name() + " doesn't support keyword arguments", c->pstate(), traces); } - String_Quoted_Ptr str = SASS_MEMORY_NEW(String_Quoted, + String_Quoted* str = SASS_MEMORY_NEW(String_Quoted, c->pstate(), - lit->to_string(ctx.c_options)); + lit->to_string(options())); str->is_interpolant(c->is_interpolant()); return str; } else { @@ -961,7 +1005,7 @@ namespace Sass { if (full_name != "if[f]") { args = Cast(args->perform(this)); } - Definition_Ptr def = Cast((*env)[full_name]); + Definition* def = Cast((*env)[full_name]); if (c->func()) def = c->func()->definition(); @@ -971,7 +1015,7 @@ namespace Sass { // account for rest arguments if (args->has_rest_argument() && args->length() > 0) { // get the rest arguments list - List_Ptr rest = Cast(args->last()->value()); + List* rest = Cast(args->last()->value()); // arguments before rest argument plus rest if (rest) L += rest->length() - 1; } @@ -991,13 +1035,13 @@ namespace Sass { Parameters_Obj params = def->parameters(); Env fn_env(def->environment()); - exp.env_stack.push_back(&fn_env); + env_stack().push_back(&fn_env); if (func || body) { - bind(std::string("Function"), c->name(), params, args, &ctx, &fn_env, this); + bind(std::string("Function"), c->name(), params, args, &fn_env, this, traces); std::string msg(", in function `" + c->name() + "`"); traces.push_back(Backtrace(c->pstate(), msg)); - ctx.callee_stack.push_back({ + callee_stack().push_back({ c->name().c_str(), c->pstate().path, c->pstate().line + 1, @@ -1011,12 +1055,12 @@ namespace Sass { result = body->perform(this); } else if (func) { - result = func(fn_env, *env, ctx, def->signature(), c->pstate(), traces, exp.selector_stack); + result = func(fn_env, *env, ctx, def->signature(), c->pstate(), traces, exp.getSelectorStack(), exp.originalStack); } if (!result) { error(std::string("Function ") + c->name() + " finished without @return", c->pstate(), traces); } - ctx.callee_stack.pop_back(); + callee_stack().pop_back(); traces.pop_back(); } @@ -1034,10 +1078,10 @@ namespace Sass { // populates env with default values for params std::string ff(c->name()); - bind(std::string("Function"), c->name(), params, args, &ctx, &fn_env, this); + bind(std::string("Function"), c->name(), params, args, &fn_env, this, traces); std::string msg(", in function `" + c->name() + "`"); traces.push_back(Backtrace(c->pstate(), msg)); - ctx.callee_stack.push_back({ + callee_stack().push_back({ c->name().c_str(), c->pstate().path, c->pstate().line + 1, @@ -1046,24 +1090,30 @@ namespace Sass { { env } }); - To_C to_c; + 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); std::string key = param->name(); AST_Node_Obj node = fn_env.get_local(key); Expression_Obj arg = Cast(node); - sass_list_set_value(c_args, i, arg->perform(&to_c)); + sass_list_set_value(c_args, i, arg->perform(&ast2c)); } - union Sass_Value* c_val = c_func(c_args, c_function, ctx.c_compiler); + union Sass_Value* c_val = c_func(c_args, c_function, compiler()); if (sass_value_get_tag(c_val) == SASS_ERROR) { - error("error in C function " + c->name() + ": " + sass_error_get_message(c_val), c->pstate(), traces); + std::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) { - error("warning in C function " + c->name() + ": " + sass_warning_get_message(c_val), c->pstate(), traces); + std::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 = cval_to_astnode(c_val, traces, c->pstate()); + result = c2ast(c_val, traces, c->pstate()); - ctx.callee_stack.pop_back(); + callee_stack().pop_back(); traces.pop_back(); sass_delete_value(c_args); if (c_val != c_args) @@ -1077,30 +1127,20 @@ namespace Sass { result = result->perform(this); result->is_interpolant(c->is_interpolant()); - exp.env_stack.pop_back(); + env_stack().pop_back(); return result.detach(); } - Expression_Ptr Eval::operator()(Function_Call_Schema_Ptr s) - { - Expression_Ptr evaluated_name = s->name()->perform(this); - Expression_Ptr evaluated_args = s->arguments()->perform(this); - String_Schema_Obj ss = SASS_MEMORY_NEW(String_Schema, s->pstate(), 2); - ss->append(evaluated_name); - ss->append(evaluated_args); - return ss->perform(this); - } - - Expression_Ptr Eval::operator()(Variable_Ptr v) + Expression* Eval::operator()(Variable* v) { - Expression_Obj value = 0; + Expression_Obj value; Env* env = environment(); const std::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_Ptr arg = Cast(value)) value = arg->value(); - if (Number_Ptr nr = Cast(value)) nr->zero(true); // force flag + 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 @@ -1109,17 +1149,22 @@ namespace Sass { return value.detach(); } - Expression_Ptr Eval::operator()(Color_Ptr c) + Expression* Eval::operator()(Color_RGBA* c) { return c; } - Expression_Ptr Eval::operator()(Number_Ptr n) + Expression* Eval::operator()(Color_HSLA* c) + { + return c; + } + + Expression* Eval::operator()(Number* n) { return n; } - Expression_Ptr Eval::operator()(Boolean_Ptr b) + Expression* Eval::operator()(Boolean* b) { return b; } @@ -1128,8 +1173,8 @@ namespace Sass { bool needs_closing_brace = false; - if (Arguments_Ptr args = Cast(ex)) { - List_Ptr ll = SASS_MEMORY_NEW(List, args->pstate(), 0, SASS_COMMA); + 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()); } @@ -1138,7 +1183,7 @@ namespace Sass { res += "("; ex = ll; } - if (Number_Ptr nr = Cast(ex)) { + if (Number* nr = Cast(ex)) { Number reduced(nr); reduced.reduce(); if (!reduced.is_valid_css_unit()) { @@ -1146,10 +1191,10 @@ namespace Sass { throw Exception::InvalidValue(traces, *nr); } } - if (Argument_Ptr arg = Cast(ex)) { + if (Argument* arg = Cast(ex)) { ex = arg->value(); } - if (String_Quoted_Ptr sq = Cast(ex)) { + 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()); @@ -1160,12 +1205,12 @@ namespace Sass { if (Cast(ex)) { return; } // parent selector needs another go - if (Cast(ex)) { + if (Cast(ex)) { // XXX: this is never hit via spec tests ex = ex->perform(this); } - if (List_Ptr l = Cast(ex)) { + 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 += ""; } @@ -1179,12 +1224,12 @@ namespace Sass { // here. Normally single list items are already unwrapped. if (l->size() > 1) { // string_to_output would fail "#{'_\a' '_\a'}"; - std::string str(ll->to_string(ctx.c_options)); + std::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(ctx.c_options)); + res += (ll->to_string(options())); } ll->is_interpolant(l->is_interpolant()); } @@ -1194,14 +1239,13 @@ namespace Sass { // Selector_List // String_Quoted // String_Constant - // Parent_Selector // Binary_Expression else { // ex = ex->perform(this); if (into_quotes && ex->is_interpolant()) { - res += evacuate_escapes(ex ? ex->to_string(ctx.c_options) : ""); + res += evacuate_escapes(ex ? ex->to_string(options()) : ""); } else { - std::string str(ex ? ex->to_string(ctx.c_options) : ""); + std::string str(ex ? ex->to_string(options()) : ""); if (into_quotes) str = read_hex_escapes(str); res += str; // append to result string } @@ -1211,14 +1255,14 @@ namespace Sass { } - Expression_Ptr Eval::operator()(String_Schema_Ptr s) + 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_Ptr l = Cast((*s)[0])) { - if (String_Constant_Ptr r = 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; @@ -1247,7 +1291,7 @@ namespace Sass { // 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_Ptr str = SASS_MEMORY_NEW(String_Constant, s->pstate(), res); + // 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()); @@ -1255,25 +1299,25 @@ namespace Sass { } - Expression_Ptr Eval::operator()(String_Constant_Ptr s) + Expression* Eval::operator()(String_Constant* s) { return s; } - Expression_Ptr Eval::operator()(String_Quoted_Ptr s) + Expression* Eval::operator()(String_Quoted* s) { - String_Quoted_Ptr str = SASS_MEMORY_NEW(String_Quoted, s->pstate(), ""); + 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_Ptr Eval::operator()(Supports_Operator_Ptr c) + Expression* Eval::operator()(Supports_Operator* c) { - Expression_Ptr left = c->left()->perform(this); - Expression_Ptr right = c->right()->perform(this); - Supports_Operator_Ptr cc = SASS_MEMORY_NEW(Supports_Operator, + Expression* left = c->left()->perform(this); + Expression* right = c->right()->perform(this); + Supports_Operator* cc = SASS_MEMORY_NEW(Supports_Operator, c->pstate(), Cast(left), Cast(right), @@ -1281,52 +1325,52 @@ namespace Sass { return cc; } - Expression_Ptr Eval::operator()(Supports_Negation_Ptr c) + Expression* Eval::operator()(Supports_Negation* c) { - Expression_Ptr condition = c->condition()->perform(this); - Supports_Negation_Ptr cc = SASS_MEMORY_NEW(Supports_Negation, + Expression* condition = c->condition()->perform(this); + Supports_Negation* cc = SASS_MEMORY_NEW(Supports_Negation, c->pstate(), Cast(condition)); return cc; } - Expression_Ptr Eval::operator()(Supports_Declaration_Ptr c) + Expression* Eval::operator()(Supports_Declaration* c) { - Expression_Ptr feature = c->feature()->perform(this); - Expression_Ptr value = c->value()->perform(this); - Supports_Declaration_Ptr cc = SASS_MEMORY_NEW(Supports_Declaration, + Expression* feature = c->feature()->perform(this); + Expression* value = c->value()->perform(this); + Supports_Declaration* cc = SASS_MEMORY_NEW(Supports_Declaration, c->pstate(), feature, value); return cc; } - Expression_Ptr Eval::operator()(Supports_Interpolation_Ptr c) + Expression* Eval::operator()(Supports_Interpolation* c) { - Expression_Ptr value = c->value()->perform(this); - Supports_Interpolation_Ptr cc = SASS_MEMORY_NEW(Supports_Interpolation, + Expression* value = c->value()->perform(this); + Supports_Interpolation* cc = SASS_MEMORY_NEW(Supports_Interpolation, c->pstate(), value); return cc; } - Expression_Ptr Eval::operator()(At_Root_Query_Ptr e) + Expression* Eval::operator()(At_Root_Query* e) { Expression_Obj feature = e->feature(); feature = (feature ? feature->perform(this) : 0); Expression_Obj value = e->value(); value = (value ? value->perform(this) : 0); - Expression_Ptr ee = SASS_MEMORY_NEW(At_Root_Query, + Expression* ee = SASS_MEMORY_NEW(At_Root_Query, e->pstate(), Cast(feature), value); return ee; } - Media_Query_Ptr Eval::operator()(Media_Query_Ptr q) + Media_Query* Eval::operator()(Media_Query* q) { String_Obj t = q->media_type(); - t = static_cast(t.isNull() ? 0 : t->perform(this)); + t = static_cast(t.isNull() ? 0 : t->perform(this)); Media_Query_Obj qq = SASS_MEMORY_NEW(Media_Query, q->pstate(), t, @@ -1334,12 +1378,12 @@ namespace Sass { 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))); + qq->append(static_cast((*q)[i]->perform(this))); } return qq.detach(); } - Expression_Ptr Eval::operator()(Media_Query_Expression_Ptr e) + Expression* Eval::operator()(Media_Query_Expression* e) { Expression_Obj feature = e->feature(); feature = (feature ? feature->perform(this) : 0); @@ -1363,12 +1407,12 @@ namespace Sass { e->is_interpolated()); } - Expression_Ptr Eval::operator()(Null_Ptr n) + Expression* Eval::operator()(Null* n) { return n; } - Expression_Ptr Eval::operator()(Argument_Ptr a) + Expression* Eval::operator()(Argument* a) { Expression_Obj val = a->value()->perform(this); bool is_rest_argument = a->is_rest_argument(); @@ -1397,13 +1441,13 @@ namespace Sass { is_keyword_argument); } - Expression_Ptr Eval::operator()(Arguments_Ptr a) + 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) { Expression_Obj rv = (*a)[i]->perform(this); - Argument_Ptr arg = Cast(rv); + Argument* arg = Cast(rv); if (!(arg->is_rest_argument() || arg->is_keyword_argument())) { aa->append(arg); } @@ -1414,8 +1458,8 @@ namespace Sass { Expression_Obj splat = Cast(rest)->value()->perform(this); Sass_Separator separator = SASS_COMMA; - List_Ptr ls = Cast(splat); - Map_Ptr ms = Cast(splat); + List* ls = Cast(splat); + Map* ms = Cast(splat); List_Obj arglist = SASS_MEMORY_NEW(List, splat->pstate(), @@ -1439,7 +1483,7 @@ namespace Sass { if (a->has_keyword_argument()) { Expression_Obj rv = a->get_keyword_argument()->perform(this); - Argument_Ptr rvarg = Cast(rv); + Argument* rvarg = Cast(rv); Expression_Obj kwarg = rvarg->value()->perform(this); aa->append(SASS_MEMORY_NEW(Argument, kwarg->pstate(), kwarg, "", false, true)); @@ -1447,217 +1491,47 @@ namespace Sass { return aa.detach(); } - Expression_Ptr Eval::operator()(Comment_Ptr c) + Expression* Eval::operator()(Comment* c) { return 0; } - inline Expression_Ptr Eval::fallback_impl(AST_Node_Ptr n) - { - return static_cast(n); - } - - // All the binary helpers. - - Expression_Ptr cval_to_astnode(union Sass_Value* v, Backtraces traces, ParserState pstate) - { - using std::strlen; - using std::strcpy; - Expression_Ptr 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, 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_Ptr 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(cval_to_astnode(sass_list_get_value(v, i), traces, pstate)); - } - l->is_bracketed(sass_list_get_is_bracketed(v)); - e = l; - } break; - case SASS_MAP: { - Map_Ptr m = SASS_MEMORY_NEW(Map, pstate); - for (size_t i = 0, L = sass_map_get_length(v); i < L; ++i) { - *m << std::make_pair( - cval_to_astnode(sass_map_get_key(v, i), traces, pstate), - cval_to_astnode(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: " + std::string(sass_error_get_message(v)), pstate, traces); - } break; - case SASS_WARNING: { - error("Warning in C function: " + std::string(sass_warning_get_message(v)), pstate, traces); - } break; - default: break; - } - return e; - } - - Selector_List_Ptr Eval::operator()(Selector_List_Ptr s) - { - std::vector rv; - Selector_List_Obj sl = SASS_MEMORY_NEW(Selector_List, s->pstate()); - sl->is_optional(s->is_optional()); - sl->media_block(s->media_block()); - sl->is_optional(s->is_optional()); - for (size_t i = 0, iL = s->length(); i < iL; ++i) { - rv.push_back(operator()((*s)[i])); - } - - // we should actually permutate parent first - // but here we have permutated the selector first - size_t round = 0; - while (round != std::string::npos) { - bool abort = true; - for (size_t i = 0, iL = rv.size(); i < iL; ++i) { - if (rv[i]->length() > round) { - sl->append((*rv[i])[round]); - abort = false; - } - } - if (abort) { - round = std::string::npos; - } else { - ++ round; - } - - } - return sl.detach(); - } - - - Selector_List_Ptr Eval::operator()(Complex_Selector_Ptr s) - { - bool implicit_parent = !exp.old_at_root_without_rule; - if (is_in_selector_schema) exp.selector_stack.push_back(0); - Selector_List_Obj resolved = s->resolve_parent_refs(exp.selector_stack, traces, implicit_parent); - if (is_in_selector_schema) exp.selector_stack.pop_back(); - for (size_t i = 0; i < resolved->length(); i++) { - Complex_Selector_Ptr is = resolved->at(i)->first(); - while (is) { - if (is->head()) { - is->head(operator()(is->head())); - } - is = is->tail(); - } - } - return resolved.detach(); - } - - Compound_Selector_Ptr Eval::operator()(Compound_Selector_Ptr s) - { - for (size_t i = 0; i < s->length(); i++) { - Simple_Selector_Ptr ss = s->at(i); - // skip parents here (called via resolve_parent_refs) - if (ss == NULL || Cast(ss)) continue; - s->at(i) = Cast(ss->perform(this)); - } - return s; - } - - Selector_List_Ptr Eval::operator()(Selector_Schema_Ptr s) + SelectorList* Eval::operator()(Selector_Schema* s) { LOCAL_FLAG(is_in_selector_schema, true); // the parser will look for a brace to end the selector - ctx.c_options.in_selector = true; // do not compress colors Expression_Obj sel = s->contents()->perform(this); - std::string result_str(sel->to_string(ctx.c_options)); - ctx.c_options.in_selector = false; // flag temporary only + std::string result_str(sel->to_string(options())); result_str = unquote(Util::rtrim(result_str)); char* temp_cstr = sass_copy_c_string(result_str.c_str()); ctx.strings.push_back(temp_cstr); // attach to context Parser p = Parser::from_c_str(temp_cstr, ctx, traces, s->pstate()); - p.last_media_block = s->media_block(); - // a selector schema may or may not connect to parent? - bool chroot = s->connect_parent() == false; - Selector_List_Obj sl = p.parse_selector_list(chroot); - auto vec_str_rend = ctx.strings.rend(); - auto vec_str_rbegin = ctx.strings.rbegin(); - // remove the first item searching from the back - // we cannot assume our item is still the last one - // order is not important, so we can optimize this - auto it = std::find(vec_str_rbegin, vec_str_rend, temp_cstr); - // undefined behavior if not found! - if (it != vec_str_rend) { - // overwrite with last item - *it = ctx.strings.back(); - // remove last one from vector - ctx.strings.pop_back(); - // free temporary copy - free(temp_cstr); - } + + // 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 operator()(sl); + return parsed.detach(); } - Expression_Ptr Eval::operator()(Parent_Selector_Ptr p) + Expression* Eval::operator()(Parent_Reference* p) { - if (Selector_List_Obj pr = selector()) { - exp.selector_stack.pop_back(); - Selector_List_Obj rv = operator()(pr); - exp.selector_stack.push_back(rv); - return rv.detach(); + if (SelectorListObj pr = exp.original()) { + return operator()(pr); } else { return SASS_MEMORY_NEW(Null, p->pstate()); } } - Simple_Selector_Ptr Eval::operator()(Simple_Selector_Ptr s) + SimpleSelector* Eval::operator()(SimpleSelector* s) { return s; } - // hotfix to avoid invalid nested `:not` selectors - // probably the wrong place, but this should ultimately - // be fixed by implement superselector correctly for `:not` - // first use of "find" (ATM only implemented for selectors) - bool hasNotSelector(AST_Node_Obj obj) { - if (Wrapped_Selector_Ptr w = Cast(obj)) { - return w->name() == ":not"; - } - return false; - } - - Wrapped_Selector_Ptr Eval::operator()(Wrapped_Selector_Ptr s) + Pseudo_Selector* Eval::operator()(Pseudo_Selector* pseudo) { - - if (s->name() == ":not") { - if (exp.selector_stack.back()) { - if (s->selector()->find(hasNotSelector)) { - s->selector()->clear(); - s->name(" "); - } else if (s->selector()->length() == 1) { - Complex_Selector_Ptr cs = s->selector()->at(0); - if (cs->tail()) { - s->selector()->clear(); - s->name(" "); - } - } else if (s->selector()->length() > 1) { - s->selector()->clear(); - s->name(" "); - } - } - } - return s; + // ToDo: should we eval selector? + return pseudo; }; } diff --git a/src/libsass/src/eval.hpp b/src/libsass/src/eval.hpp index aeaada87e..c27db2fff 100644 --- a/src/libsass/src/eval.hpp +++ b/src/libsass/src/eval.hpp @@ -1,7 +1,11 @@ #ifndef SASS_EVAL_H #define SASS_EVAL_H +// sass.hpp must go before all system headers to get the +// __EXTENSIONS__ fix on Solaris. +#include "sass.hpp" #include "ast.hpp" + #include "context.hpp" #include "listize.hpp" #include "operation.hpp" @@ -12,10 +16,7 @@ namespace Sass { class Expand; class Context; - class Eval : public Operation_CRTP { - - private: - Expression_Ptr fallback_impl(AST_Node_Ptr n); + class Eval : public Operation_CRTP { public: Expand& exp; @@ -32,72 +33,78 @@ namespace Sass { Boolean_Obj bool_false; Env* environment(); - Selector_List_Obj selector(); + EnvStack& env_stack(); + const std::string cwd(); + CalleeStack& callee_stack(); + struct Sass_Inspect_Options& options(); + struct Sass_Compiler* compiler(); // for evaluating function bodies - Expression_Ptr operator()(Block_Ptr); - Expression_Ptr operator()(Assignment_Ptr); - Expression_Ptr operator()(If_Ptr); - Expression_Ptr operator()(For_Ptr); - Expression_Ptr operator()(Each_Ptr); - Expression_Ptr operator()(While_Ptr); - Expression_Ptr operator()(Return_Ptr); - Expression_Ptr operator()(Warning_Ptr); - Expression_Ptr operator()(Error_Ptr); - Expression_Ptr operator()(Debug_Ptr); - - Expression_Ptr operator()(List_Ptr); - Expression_Ptr operator()(Map_Ptr); - Expression_Ptr operator()(Binary_Expression_Ptr); - Expression_Ptr operator()(Unary_Expression_Ptr); - Expression_Ptr operator()(Function_Call_Ptr); - Expression_Ptr operator()(Function_Call_Schema_Ptr); - Expression_Ptr operator()(Variable_Ptr); - Expression_Ptr operator()(Number_Ptr); - Expression_Ptr operator()(Color_Ptr); - Expression_Ptr operator()(Boolean_Ptr); - Expression_Ptr operator()(String_Schema_Ptr); - Expression_Ptr operator()(String_Quoted_Ptr); - Expression_Ptr operator()(String_Constant_Ptr); - // Expression_Ptr operator()(Selector_List_Ptr); - Media_Query_Ptr operator()(Media_Query_Ptr); - Expression_Ptr operator()(Media_Query_Expression_Ptr); - Expression_Ptr operator()(At_Root_Query_Ptr); - Expression_Ptr operator()(Supports_Operator_Ptr); - Expression_Ptr operator()(Supports_Negation_Ptr); - Expression_Ptr operator()(Supports_Declaration_Ptr); - Expression_Ptr operator()(Supports_Interpolation_Ptr); - Expression_Ptr operator()(Null_Ptr); - Expression_Ptr operator()(Argument_Ptr); - Expression_Ptr operator()(Arguments_Ptr); - Expression_Ptr operator()(Comment_Ptr); + Expression* operator()(Block*); + Expression* operator()(Assignment*); + Expression* operator()(If*); + Expression* operator()(For*); + Expression* operator()(Each*); + Expression* operator()(While*); + Expression* operator()(Return*); + Expression* operator()(Warning*); + Expression* operator()(Error*); + Expression* operator()(Debug*); + + 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()(Supports_Operator*); + Expression* operator()(Supports_Negation*); + Expression* operator()(Supports_Declaration*); + Expression* operator()(Supports_Interpolation*); + Expression* operator()(Null*); + Expression* operator()(Argument*); + Expression* operator()(Arguments*); + Expression* operator()(Comment*); // these will return selectors - Selector_List_Ptr operator()(Selector_List_Ptr); - Selector_List_Ptr operator()(Complex_Selector_Ptr); - Compound_Selector_Ptr operator()(Compound_Selector_Ptr); - Simple_Selector_Ptr operator()(Simple_Selector_Ptr s); - Wrapped_Selector_Ptr operator()(Wrapped_Selector_Ptr s); + SelectorList* operator()(SelectorList*); + SelectorList* operator()(ComplexSelector*); + CompoundSelector* operator()(CompoundSelector*); + SelectorComponent* operator()(SelectorComponent*); + SimpleSelector* operator()(SimpleSelector* s); + Pseudo_Selector* operator()(Pseudo_Selector* s); + // they don't have any specific implementation (yet) - // Element_Selector_Ptr operator()(Element_Selector_Ptr s) { return s; }; - // Pseudo_Selector_Ptr operator()(Pseudo_Selector_Ptr s) { return s; }; - // Class_Selector_Ptr operator()(Class_Selector_Ptr s) { return s; }; - // Id_Selector_Ptr operator()(Id_Selector_Ptr s) { return s; }; - // Placeholder_Selector_Ptr operator()(Placeholder_Selector_Ptr s) { return s; }; + Id_Selector* operator()(Id_Selector* s) { return s; }; + Class_Selector* operator()(Class_Selector* s) { return s; }; + Type_Selector* operator()(Type_Selector* s) { return s; }; + Attribute_Selector* operator()(Attribute_Selector* s) { return s; }; + Placeholder_Selector* operator()(Placeholder_Selector* s) { return s; }; + // actual evaluated selectors - Selector_List_Ptr operator()(Selector_Schema_Ptr); - Expression_Ptr operator()(Parent_Selector_Ptr); + SelectorList* operator()(Selector_Schema*); + Expression* operator()(Parent_Reference*); + // generic fallback template - Expression_Ptr fallback(U x) { return fallback_impl(x); } + Expression* fallback(U x) + { return Cast(x); } private: void interpolation(Context& ctx, std::string& res, Expression_Obj ex, bool into_quotes, bool was_itpl = false); }; - Expression_Ptr cval_to_astnode(union Sass_Value* v, Backtraces traces, ParserState pstate = ParserState("[AST]")); - } #endif diff --git a/src/libsass/src/eval_selectors.cpp b/src/libsass/src/eval_selectors.cpp new file mode 100644 index 000000000..db797e5bd --- /dev/null +++ b/src/libsass/src/eval_selectors.cpp @@ -0,0 +1,75 @@ +// sass.hpp must go before all system headers to get the +// __EXTENSIONS__ fix on Solaris. +#include "sass.hpp" +#include "expand.hpp" +#include "eval.hpp" +#include "ast.hpp" + + +namespace Sass { + + SelectorList* Eval::operator()(SelectorList* s) + { + std::vector rv; + SelectorListObj sl = SASS_MEMORY_NEW(SelectorList, s->pstate()); + for (size_t i = 0, iL = s->length(); i < iL; ++i) { + rv.push_back(operator()(s->get(i))); + } + + // we should actually permutate parent first + // but here we have permutated the selector first + size_t round = 0; + while (round != std::string::npos) { + bool abort = true; + for (size_t i = 0, iL = rv.size(); i < iL; ++i) { + if (rv[i]->length() > round) { + sl->append((*rv[i])[round]); + abort = false; + } + } + if (abort) { + round = std::string::npos; + } + else { + ++round; + } + + } + return sl.detach(); + } + + SelectorComponent* Eval::operator()(SelectorComponent* s) + { + return {}; + } + + SelectorList* Eval::operator()(ComplexSelector* s) + { + bool implicit_parent = !exp.old_at_root_without_rule; + if (is_in_selector_schema) exp.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/libsass/src/expand.cpp b/src/libsass/src/expand.cpp index d8dc03f14..eb962b1b4 100644 --- a/src/libsass/src/expand.cpp +++ b/src/libsass/src/expand.cpp @@ -1,4 +1,7 @@ +// sass.hpp must go before all system headers to get the +// __EXTENSIONS__ fix on Solaris. #include "sass.hpp" + #include #include @@ -16,7 +19,7 @@ namespace Sass { // simple endless recursion protection const size_t maxRecursion = 500; - Expand::Expand(Context& ctx, Env* env, std::vector* stack) + Expand::Expand(Context& ctx, Env* env, SelectorStack* stack, SelectorStack* originals) : ctx(ctx), traces(ctx.traces), eval(Eval(*this)), @@ -24,19 +27,32 @@ namespace Sass { in_keyframes(false), at_root_without_rule(false), old_at_root_without_rule(false), - env_stack(std::vector()), - block_stack(std::vector()), - call_stack(std::vector()), - selector_stack(std::vector()), - media_block_stack(std::vector()) + env_stack(), + block_stack(), + call_stack(), + selector_stack(), + originalStack(), + mediaStack() { - env_stack.push_back(0); + env_stack.push_back(nullptr); env_stack.push_back(env); - block_stack.push_back(0); - call_stack.push_back(0); - if (stack == NULL) { selector_stack.push_back(0); } - else { selector_stack.insert(selector_stack.end(), stack->begin(), stack->end()); } - media_block_stack.push_back(0); + 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() @@ -46,15 +62,84 @@ namespace Sass { return 0; } - Selector_List_Obj Expand::selector() + void Expand::pushNullSelector() { - if (selector_stack.size() > 0) - return selector_stack.back(); - return 0; + 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_Ptr Expand::operator()(Block_Ptr b) + Block* Expand::operator()(Block* b) { // create new local environment // set the current env as parent @@ -77,76 +162,62 @@ namespace Sass { return bb.detach(); } - Statement_Ptr Expand::operator()(Ruleset_Ptr r) + Statement* Expand::operator()(Ruleset* r) { LOCAL_FLAG(old_at_root_without_rule, at_root_without_rule); if (in_keyframes) { - Block_Ptr bb = operator()(r->block()); + Block* bb = operator()(r->block()); Keyframe_Rule_Obj k = SASS_MEMORY_NEW(Keyframe_Rule, r->pstate(), bb); - if (r->selector()) { - if (Selector_List_Ptr s = r->selector()) { - selector_stack.push_back(0); - k->name(s->eval(eval)); - selector_stack.pop_back(); + 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(); } - // reset when leaving scope - LOCAL_FLAG(at_root_without_rule, false); - - // `&` is allowed in `@at-root`! - bool has_parent_selector = false; - for (size_t i = 0, L = selector_stack.size(); i < L && !has_parent_selector; i++) { - Selector_List_Obj ll = selector_stack.at(i); - has_parent_selector = ll != 0 && ll->length() > 0; - } - - Selector_List_Obj sel = r->selector(); - if (sel) sel = sel->eval(eval); - - // check for parent selectors in base level rules - if (r->is_root() || (block_stack.back() && block_stack.back()->is_root())) { - if (Selector_List_Ptr selector_list = Cast(r->selector())) { - for (Complex_Selector_Obj complex_selector : selector_list->elements()) { - Complex_Selector_Ptr tail = complex_selector; - while (tail) { - if (tail->head()) for (Simple_Selector_Obj header : tail->head()->elements()) { - Parent_Selector_Ptr ptr = Cast(header); - if (ptr == NULL || (!ptr->real() || has_parent_selector)) continue; - std::string sel_str(complex_selector->to_string(ctx.c_options)); - error("Base-level rules cannot contain the parent-selector-referencing character '&'.", header->pstate(), traces); - } - tail = tail->tail(); - } - } - } - } - else { - if (sel->length() == 0 || sel->has_parent_ref()) { - if (sel->has_real_parent_ref() && !has_parent_selector) { - error("Base-level rules cannot contain the parent-selector-referencing character '&'.", sel->pstate(), traces); - } + if (r->schema()) { + SelectorListObj sel = eval(r->schema()); + r->selector(sel); + bool chroot = sel->has_real_parent_ref(); + for (auto complex : sel->elements()) { + complex->chroots(chroot); } + } + // reset when leaving scope + LOCAL_FLAG(at_root_without_rule, false); + + SelectorListObj evaled = eval(r->selector()); // do not connect parent again - sel->remove_parent_selectors(); - selector_stack.push_back(sel); Env env(environment()); if (block_stack.back()->is_root()) { env_stack.push_back(&env); } - sel->set_media_block(media_block_stack.back()); - Block_Obj blk = 0; + 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()); - Ruleset_Ptr rr = SASS_MEMORY_NEW(Ruleset, + popFromOriginalStack(); + popFromSelectorStack(); + Ruleset* rr = SASS_MEMORY_NEW(Ruleset, r->pstate(), - sel, + evaled, blk); - selector_stack.pop_back(); + if (block_stack.back()->is_root()) { env_stack.pop_back(); } @@ -157,7 +228,7 @@ namespace Sass { return rr; } - Statement_Ptr Expand::operator()(Supports_Block_Ptr f) + Statement* Expand::operator()(Supports_Block* f) { Expression_Obj condition = f->condition()->perform(&eval); Supports_Block_Obj ff = SASS_MEMORY_NEW(Supports_Block, @@ -167,34 +238,47 @@ namespace Sass { return ff.detach(); } - Statement_Ptr Expand::operator()(Media_Block_Ptr m) + std::vector Expand::mergeMediaQueries( + const std::vector& lhs, + const std::vector& rhs) { - Media_Block_Obj cpy = SASS_MEMORY_COPY(m); - // Media_Blocks are prone to have circular references - // Copy could leak memory if it does not get picked up - // Looks like we are able to reset block reference for copy - // Good as it will ensure a low memory overhead for this fix - // So this is a cheap solution with a minimal price - ctx.ast_gc.push_back(cpy); cpy->block(0); - Expression_Obj mq = eval(m->media_queries()); - std::string str_mq(mq->to_string(ctx.c_options)); + std::vector queries; + for (CssMediaQuery_Obj query1 : lhs) { + for (CssMediaQuery_Obj query2 : rhs) { + CssMediaQuery_Obj result = query1->merge(query2); + if (result && !result->empty()) { + queries.push_back(result); + } + } + } + return queries; + } + + Statement* Expand::operator()(MediaRule* m) + { + Expression_Obj mq = eval(m->schema()); + std::string str_mq(mq->to_css(ctx.c_options)); char* str = sass_copy_c_string(str_mq.c_str()); ctx.strings.push_back(str); - Parser p(Parser::from_c_str(str, ctx, traces, mq->pstate())); - mq = p.parse_media_queries(); // re-assign now - cpy->media_queries(mq); - media_block_stack.push_back(cpy); - Block_Obj blk = operator()(m->block()); - Media_Block_Ptr mm = SASS_MEMORY_NEW(Media_Block, - m->pstate(), - mq, - blk); - media_block_stack.pop_back(); - mm->tabs(m->tabs()); - return mm; - } - - Statement_Ptr Expand::operator()(At_Root_Block_Ptr a) + Parser parser(Parser::from_c_str(str, ctx, traces, mq->pstate())); + // Create a new CSS only representation of the media rule + CssMediaRuleObj css = SASS_MEMORY_NEW(CssMediaRule, m->pstate(), m->block()); + std::vector parsed = parser.parseCssMediaQueries(); + if (mediaStack.size() && mediaStack.back()) { + auto& parent = mediaStack.back()->elements(); + css->concat(mergeMediaQueries(parent, parsed)); + } + else { + css->concat(parsed); + } + mediaStack.push_back(css); + css->block(operator()(m->block())); + mediaStack.pop_back(); + return css.detach(); + + } + + Statement* Expand::operator()(At_Root_Block* a) { Block_Obj ab = a->block(); Expression_Obj ae = a->expression(); @@ -202,7 +286,7 @@ namespace Sass { if (ae) ae = ae->perform(&eval); else ae = SASS_MEMORY_NEW(At_Root_Query, a->pstate()); - LOCAL_FLAG(at_root_without_rule, true); + LOCAL_FLAG(at_root_without_rule, Cast(ae)->exclude("rule")); LOCAL_FLAG(in_keyframes, false); ; @@ -215,18 +299,18 @@ namespace Sass { return aa.detach(); } - Statement_Ptr Expand::operator()(Directive_Ptr a) + Statement* Expand::operator()(Directive* a) { LOCAL_FLAG(in_keyframes, a->is_keyframes()); - Block_Ptr ab = a->block(); - Selector_List_Ptr as = a->selector(); - Expression_Ptr av = a->value(); - selector_stack.push_back(0); + Block* ab = a->block(); + SelectorList* as = a->selector(); + Expression* av = a->value(); + pushNullSelector(); if (av) av = av->perform(&eval); if (as) as = eval(as); - selector_stack.pop_back(); - Block_Ptr bb = ab ? operator()(ab) : NULL; - Directive_Ptr aa = SASS_MEMORY_NEW(Directive, + popNullSelector(); + Block* bb = ab ? operator()(ab) : NULL; + Directive* aa = SASS_MEMORY_NEW(Directive, a->pstate(), a->keyword(), as, @@ -235,7 +319,7 @@ namespace Sass { return aa; } - Statement_Ptr Expand::operator()(Declaration_Ptr d) + Statement* Expand::operator()(Declaration* d) { Block_Obj ab = d->block(); String_Obj old_p = d->property(); @@ -250,9 +334,15 @@ namespace Sass { if (value) value = value->perform(&eval); Block_Obj bb = ab ? operator()(ab) : NULL; if (!bb) { - if (!value || (value->is_invisible() && !d->is_important())) return 0; + 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_Ptr decl = SASS_MEMORY_NEW(Declaration, + Declaration* decl = SASS_MEMORY_NEW(Declaration, d->pstate(), new_p, value, @@ -263,7 +353,7 @@ namespace Sass { return decl; } - Statement_Ptr Expand::operator()(Assignment_Ptr a) + Statement* Expand::operator()(Assignment* a) { Env* env = environment(); const std::string& var(a->variable()); @@ -324,7 +414,7 @@ namespace Sass { return 0; } - Statement_Ptr Expand::operator()(Import_Ptr imp) + Statement* Expand::operator()(Import* imp) { Import_Obj result = SASS_MEMORY_NEW(Import, imp->pstate()); if (imp->import_queries() && imp->import_queries()->size()) { @@ -339,7 +429,7 @@ namespace Sass { return result.detach(); } - Statement_Ptr Expand::operator()(Import_Stub_Ptr i) + Statement* Expand::operator()(Import_Stub* i) { traces.push_back(Backtrace(i->pstate())); // get parent node from call stack @@ -369,28 +459,28 @@ namespace Sass { return 0; } - Statement_Ptr Expand::operator()(Warning_Ptr w) + Statement* Expand::operator()(Warning* w) { // eval handles this too, because warnings may occur in functions w->perform(&eval); return 0; } - Statement_Ptr Expand::operator()(Error_Ptr e) + Statement* Expand::operator()(Error* e) { // eval handles this too, because errors may occur in functions e->perform(&eval); return 0; } - Statement_Ptr Expand::operator()(Debug_Ptr d) + Statement* Expand::operator()(Debug* d) { // eval handles this too, because warnings may occur in functions d->perform(&eval); return 0; } - Statement_Ptr Expand::operator()(Comment_Ptr c) + Statement* Expand::operator()(Comment* c) { if (ctx.output_style() == COMPRESSED) { // comments should not be evaluated in compact @@ -398,13 +488,13 @@ namespace Sass { if (!c->is_important()) return NULL; } eval.is_in_comment = true; - Comment_Ptr rv = SASS_MEMORY_NEW(Comment, c->pstate(), Cast(c->text()->perform(&eval)), c->is_important()); + 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_Ptr Expand::operator()(If_Ptr i) + Statement* Expand::operator()(If* i) { Env env(environment(), true); env_stack.push_back(&env); @@ -414,7 +504,7 @@ namespace Sass { append_block(i->block()); } else { - Block_Ptr alt = i->alternative(); + Block* alt = i->alternative(); if (alt) append_block(alt); } call_stack.pop_back(); @@ -424,7 +514,7 @@ namespace Sass { // For does not create a new env scope // But iteration vars are reset afterwards - Statement_Ptr Expand::operator()(For_Ptr f) + Statement* Expand::operator()(For* f) { std::string variable(f->variable()); Expression_Obj low = f->lower_bound()->perform(&eval); @@ -452,7 +542,7 @@ namespace Sass { Env env(environment(), true); env_stack.push_back(&env); call_stack.push_back(f); - Block_Ptr body = f->block(); + Block* body = f->block(); if (start < end) { if (f->is_inclusive()) ++end; for (double i = start; @@ -479,18 +569,17 @@ namespace Sass { // Eval does not create a new env scope // But iteration vars are reset afterwards - Statement_Ptr Expand::operator()(Each_Ptr e) + Statement* Expand::operator()(Each* e) { std::vector variables(e->variables()); Expression_Obj expr = e->list()->perform(&eval); - List_Obj list = 0; + List_Obj list; Map_Obj map; if (expr->concrete_type() == Expression::MAP) { map = Cast(expr); } - else if (Selector_List_Ptr ls = Cast(expr)) { - Listize listize; - Expression_Obj rv = ls->perform(&listize); + else if (SelectorList * ls = Cast(expr)) { + Expression_Obj rv = Listize::perform(ls); list = Cast(rv); } else if (expr->concrete_type() != Expression::LIST) { @@ -504,7 +593,7 @@ namespace Sass { Env env(environment(), true); env_stack.push_back(&env); call_stack.push_back(e); - Block_Ptr body = e->block(); + Block* body = e->block(); if (map) { for (auto key : map->keys()) { @@ -525,7 +614,7 @@ namespace Sass { } else { // bool arglist = list->is_arglist(); - if (list->length() == 1 && Cast(list)) { + if (list->length() == 1 && Cast(list)) { list = Cast(list); } for (size_t i = 0, L = list->length(); i < L; ++i) { @@ -563,10 +652,10 @@ namespace Sass { return 0; } - Statement_Ptr Expand::operator()(While_Ptr w) + Statement* Expand::operator()(While* w) { Expression_Obj pred = w->predicate(); - Block_Ptr body = w->block(); + Block* body = w->block(); Env env(environment(), true); env_stack.push_back(&env); call_stack.push_back(w); @@ -580,96 +669,72 @@ namespace Sass { return 0; } - Statement_Ptr Expand::operator()(Return_Ptr r) + Statement* Expand::operator()(Return* r) { error("@return may only be used within a function", r->pstate(), traces); return 0; } + Statement* Expand::operator()(ExtendRule* e) + { - void Expand::expand_selector_list(Selector_Obj s, Selector_List_Obj extender) { - - if (Selector_List_Obj sl = Cast(s)) { - for (Complex_Selector_Obj complex_selector : sl->elements()) { - Complex_Selector_Obj tail = complex_selector; - while (tail) { - if (tail->head()) for (Simple_Selector_Obj header : tail->head()->elements()) { - if (Cast(header) == NULL) continue; // skip all others - std::string sel_str(complex_selector->to_string(ctx.c_options)); - error("Can't extend " + sel_str + ": can't extend parent selectors", header->pstate(), traces); - } - tail = tail->tail(); - } - } + // evaluate schema first + if (e->schema()) { + e->selector(eval(e->schema())); + e->isOptional(e->selector()->is_optional()); } + // evaluate the selector + e->selector(eval(e->selector())); + if (e->selector()) { - Selector_List_Obj contextualized = Cast(s->perform(&eval)); - if (contextualized == false) return; - for (auto complex_sel : contextualized->elements()) { - Complex_Selector_Obj c = complex_sel; - if (!c->head() || c->tail()) { - std::string sel_str(contextualized->to_string(ctx.c_options)); - error("Can't extend " + sel_str + ": can't extend nested selectors", c->pstate(), traces); - } - Compound_Selector_Obj target = c->head(); - if (contextualized->is_optional()) target->is_optional(true); - for (size_t i = 0, L = extender->length(); i < L; ++i) { - Complex_Selector_Obj sel = (*extender)[i]; - if (!(sel->head() && sel->head()->length() > 0 && - Cast((*sel->head())[0]))) - { - Compound_Selector_Obj hh = SASS_MEMORY_NEW(Compound_Selector, (*extender)[i]->pstate()); - hh->media_block((*extender)[i]->media_block()); - Complex_Selector_Obj ssel = SASS_MEMORY_NEW(Complex_Selector, (*extender)[i]->pstate()); - ssel->media_block((*extender)[i]->media_block()); - if (sel->has_line_feed()) ssel->has_line_feed(true); - Parent_Selector_Obj ps = SASS_MEMORY_NEW(Parent_Selector, (*extender)[i]->pstate()); - ps->media_block((*extender)[i]->media_block()); - hh->append(ps); - ssel->tail(sel); - ssel->head(hh); - sel = ssel; + for (auto complex : e->selector()->elements()) { + + if (complex->length() != 1) { + error("complex selectors may not be extended.", complex->pstate(), traces); } - // if (c->has_line_feed()) sel->has_line_feed(true); - ctx.subset_map.put(target, std::make_pair(sel, target)); - } - } - } + if (const CompoundSelector* compound = complex->first()->getCompound()) { + + if (compound->length() != 1) { + + std::stringstream 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()); + } - Statement* Expand::operator()(Extension_Ptr e) - { - if (Selector_List_Ptr extender = selector()) { - Selector_List_Ptr sl = e->selector(); - // abort on invalid selector - if (sl == NULL) return NULL; - if (Selector_Schema_Ptr schema = sl->schema()) { - if (schema->has_real_parent_ref()) { - // put root block on stack again (ignore parents) - // selector schema must not connect in eval! - block_stack.push_back(block_stack.at(1)); - sl = eval(sl->schema()); - block_stack.pop_back(); - } else { - selector_stack.push_back(0); - sl = eval(sl->schema()); - selector_stack.pop_back(); } - } - for (Complex_Selector_Obj cs : sl->elements()) { - if (!cs.isNull() && !cs->head().isNull()) { - cs->head()->media_block(media_block_stack.back()); + else { + error("complex selectors may not be extended.", complex->pstate(), traces); } } - selector_stack.push_back(0); - expand_selector_list(sl, extender); - selector_stack.pop_back(); } - return 0; + + return nullptr; + } - Statement_Ptr Expand::operator()(Definition_Ptr d) + Statement* Expand::operator()(Definition* d) { Env* env = environment(); Definition_Obj dd = SASS_MEMORY_COPY(d); @@ -694,8 +759,9 @@ namespace Sass { return 0; } - Statement_Ptr Expand::operator()(Mixin_Call_Ptr c) + Statement* Expand::operator()(Mixin_Call* c) { + if (recursions > maxRecursion) { throw Exception::StackError(traces, *c); } @@ -730,29 +796,31 @@ namespace Sass { 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", - SASS_MEMORY_NEW(Parameters, c->pstate()), + params, c->block(), Definition::MIXIN); thunk->environment(env); new_env.local_frame()["@content[m]"] = thunk; } - bind(std::string("Mixin"), c->name(), params, args, &ctx, &new_env, &eval); + bind(std::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_Ptr pr = block_stack.back()) { + 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 (Ruleset_Ptr r = Cast(bb)) { + if (Ruleset* r = Cast(bb)) { r->is_root(trace_block->is_root()); } Statement_Obj ith = bb->perform(this); @@ -769,45 +837,29 @@ namespace Sass { return trace.detach(); } - Statement_Ptr Expand::operator()(Content_Ptr c) + 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; - - if (block_stack.back()->is_root()) { - selector_stack.push_back(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", - SASS_MEMORY_NEW(Arguments, c->pstate())); + args); Trace_Obj trace = Cast(call->perform(this)); - - if (block_stack.back()->is_root()) { - selector_stack.pop_back(); - } - return trace.detach(); } - // produce an error if something is not implemented - inline Statement_Ptr Expand::fallback_impl(AST_Node_Ptr n) - { - std::string err =std:: string("`Expand` doesn't handle ") + typeid(*n).name(); - String_Quoted_Obj msg = SASS_MEMORY_NEW(String_Quoted, ParserState("[WARN]"), err); - error("unknown internal error; please contact the LibSass maintainers", n->pstate(), traces); - return SASS_MEMORY_NEW(Warning, ParserState("[WARN]"), msg); - } - // process and add to last block on stack - inline void Expand::append_block(Block_Ptr b) + 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_Ptr stm = b->at(i); + Statement* stm = b->at(i); Statement_Obj ith = stm->perform(this); if (ith) block_stack.back()->append(ith); } diff --git a/src/libsass/src/expand.hpp b/src/libsass/src/expand.hpp index 3464c98f6..90edbf4ca 100644 --- a/src/libsass/src/expand.hpp +++ b/src/libsass/src/expand.hpp @@ -15,11 +15,22 @@ namespace Sass { class Eval; struct Backtrace; - class Expand : public Operation_CRTP { + class Expand : public Operation_CRTP { public: Env* environment(); - Selector_List_Obj selector(); + 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; @@ -30,51 +41,56 @@ namespace Sass { bool old_at_root_without_rule; // it's easier to work with vectors - std::vector env_stack; - std::vector block_stack; - std::vector call_stack; - std::vector selector_stack; - std::vector media_block_stack; + EnvStack env_stack; + BlockStack block_stack; + CallStack call_stack; + private: + SelectorStack selector_stack; + public: + SelectorStack originalStack; + MediaStack mediaStack; Boolean_Obj bool_true; - Statement_Ptr fallback_impl(AST_Node_Ptr n); - private: - void expand_selector_list(Selector_Obj, Selector_List_Obj extender); + + std::vector mergeMediaQueries(const std::vector& lhs, const std::vector& rhs); public: - Expand(Context&, Env*, std::vector* stack = NULL); + Expand(Context&, Env*, SelectorStack* stack = nullptr, SelectorStack* original = nullptr); ~Expand() { } - Block_Ptr operator()(Block_Ptr); - Statement_Ptr operator()(Ruleset_Ptr); - Statement_Ptr operator()(Media_Block_Ptr); - Statement_Ptr operator()(Supports_Block_Ptr); - Statement_Ptr operator()(At_Root_Block_Ptr); - Statement_Ptr operator()(Directive_Ptr); - Statement_Ptr operator()(Declaration_Ptr); - Statement_Ptr operator()(Assignment_Ptr); - Statement_Ptr operator()(Import_Ptr); - Statement_Ptr operator()(Import_Stub_Ptr); - Statement_Ptr operator()(Warning_Ptr); - Statement_Ptr operator()(Error_Ptr); - Statement_Ptr operator()(Debug_Ptr); - Statement_Ptr operator()(Comment_Ptr); - Statement_Ptr operator()(If_Ptr); - Statement_Ptr operator()(For_Ptr); - Statement_Ptr operator()(Each_Ptr); - Statement_Ptr operator()(While_Ptr); - Statement_Ptr operator()(Return_Ptr); - Statement_Ptr operator()(Extension_Ptr); - Statement_Ptr operator()(Definition_Ptr); - Statement_Ptr operator()(Mixin_Call_Ptr); - Statement_Ptr operator()(Content_Ptr); - - template - Statement_Ptr fallback(U x) { return fallback_impl(x); } - - void append_block(Block_Ptr); + Block* operator()(Block*); + Statement* operator()(Ruleset*); + + Statement* operator()(MediaRule*); + + // Css Ruleset is already static + // Statement* operator()(CssMediaRule*); + + Statement* operator()(Supports_Block*); + Statement* operator()(At_Root_Block*); + Statement* operator()(Directive*); + Statement* operator()(Declaration*); + Statement* operator()(Assignment*); + Statement* operator()(Import*); + Statement* operator()(Import_Stub*); + Statement* operator()(Warning*); + Statement* operator()(Error*); + Statement* operator()(Debug*); + Statement* operator()(Comment*); + Statement* operator()(If*); + Statement* operator()(For*); + Statement* operator()(Each*); + Statement* operator()(While*); + Statement* operator()(Return*); + Statement* operator()(ExtendRule*); + Statement* operator()(Definition*); + Statement* operator()(Mixin_Call*); + Statement* operator()(Content*); + + void append_block(Block*); + }; } diff --git a/src/libsass/src/extend.cpp b/src/libsass/src/extend.cpp deleted file mode 100644 index 602269880..000000000 --- a/src/libsass/src/extend.cpp +++ /dev/null @@ -1,2130 +0,0 @@ -#include "sass.hpp" -#include "extend.hpp" -#include "context.hpp" -#include "backtrace.hpp" -#include "paths.hpp" -#include "parser.hpp" -#include "expand.hpp" -#include "node.hpp" -#include "sass_util.hpp" -#include "remove_placeholders.hpp" -#include "debug.hpp" -#include -#include -#include - -/* - NOTES: - - - The print* functions print to cerr. This allows our testing frameworks (like sass-spec) to ignore the output, which - is very helpful when debugging. The format of the output is mainly to wrap things in square brackets to match what - ruby already outputs (to make comparisons easier). - - - For the direct porting effort, we're trying to port method-for-method until we get all the tests passing. - Where applicable, I've tried to include the ruby code above the function for reference until all our tests pass. - The ruby code isn't always directly portable, so I've tried to include any modified ruby code that was actually - used for the porting. - - - DO NOT try to optimize yet. We get a tremendous benefit out of comparing the output of each stage of the extend to the ruby - output at the same stage. This makes it much easier to determine where problems are. Try to keep as close to - the ruby code as you can until we have all the sass-spec tests passing. Then, we should optimize. However, if you see - something that could probably be optimized, let's not forget it. Add a // TODO: or // IMPROVEMENT: comment. - - - Coding conventions in this file (these may need to be changed before merging back into master) - - Very basic hungarian notation: - p prefix for pointers (pSelector) - no prefix for value types and references (selector) - - Use STL iterators where possible - - prefer verbose naming over terse naming - - use typedefs for STL container types for make maintenance easier - - - You may see a lot of comments that say "// TODO: is this the correct combinator?". See the comment referring to combinators - in extendCompoundSelector for a more extensive explanation of my confusion. I think our divergence in data model from ruby - sass causes this to be necessary. - - - GLOBAL TODOS: - - - wrap the contents of the print functions in DEBUG preprocesser conditionals so they will be optimized away in non-debug mode. - - - consider making the extend* functions member functions to avoid passing around ctx and subset_map map around. This has the - drawback that the implementation details of the operator are then exposed to the outside world, which is not ideal and - can cause additional compile time dependencies. - - - mark the helper methods in this file static to given them compilation unit linkage. - - - implement parent directive matching - - - fix compilation warnings for unused Extend members if we really don't need those references anymore. - */ - - -namespace Sass { - - - -#ifdef DEBUG - - // TODO: move the ast specific ostream operators into ast.hpp/ast.cpp - std::ostream& operator<<(std::ostream& os, const Complex_Selector::Combinator combinator) { - switch (combinator) { - case Complex_Selector::ANCESTOR_OF: os << "\" \""; break; - case Complex_Selector::PARENT_OF: os << "\">\""; break; - case Complex_Selector::PRECEDES: os << "\"~\""; break; - case Complex_Selector::ADJACENT_TO: os << "\"+\""; break; - case Complex_Selector::REFERENCE: os << "\"/\""; break; - } - - return os; - } - - - std::ostream& operator<<(std::ostream& os, Compound_Selector& compoundSelector) { - for (size_t i = 0, L = compoundSelector.length(); i < L; ++i) { - if (i > 0) os << ", "; - os << compoundSelector[i]->to_string(); - } - return os; - } - - std::ostream& operator<<(std::ostream& os, Simple_Selector& simpleSelector) { - os << simpleSelector.to_string(); - return os; - } - - // Print a string representation of a Compound_Selector - static void printSimpleSelector(Simple_Selector* pSimpleSelector, const char* message=NULL, bool newline=true) { - - if (message) { - std::cerr << message; - } - - if (pSimpleSelector) { - std::cerr << "[" << *pSimpleSelector << "]"; - } else { - std::cerr << "NULL"; - } - - if (newline) { - std::cerr << std::endl; - } - } - - // Print a string representation of a Compound_Selector - static void printCompoundSelector(Compound_Selector_Ptr pCompoundSelector, const char* message=NULL, bool newline=true) { - - if (message) { - std::cerr << message; - } - - if (pCompoundSelector) { - std::cerr << "[" << *pCompoundSelector << "]"; - } else { - std::cerr << "NULL"; - } - - if (newline) { - std::cerr << std::endl; - } - } - - - std::ostream& operator<<(std::ostream& os, Complex_Selector& complexSelector) { - - os << "["; - Complex_Selector_Ptr pIter = &complexSelector; - bool first = true; - while (pIter) { - if (pIter->combinator() != Complex_Selector::ANCESTOR_OF) { - if (!first) { - os << ", "; - } - first = false; - os << pIter->combinator(); - } - - if (!first) { - os << ", "; - } - first = false; - - if (pIter->head()) { - os << pIter->head()->to_string(); - } else { - os << "NULL_HEAD"; - } - - pIter = pIter->tail(); - } - os << "]"; - - return os; - } - - - // Print a string representation of a Complex_Selector - static void printComplexSelector(Complex_Selector_Ptr pComplexSelector, const char* message=NULL, bool newline=true) { - - if (message) { - std::cerr << message; - } - - if (pComplexSelector) { - std::cerr << *pComplexSelector; - } else { - std::cerr << "NULL"; - } - - if (newline) { - std::cerr << std::endl; - } - } - - static void printSelsNewSeqPairCollection(SubSetMapLookups& collection, const char* message=NULL, bool newline=true) { - - if (message) { - std::cerr << message; - } - bool first = true; - std::cerr << "["; - for(SubSetMapLookup& pair : collection) { - if (first) { - first = false; - } else { - std::cerr << ", "; - } - std::cerr << "["; - Compound_Selector_Ptr pSels = pair.first; - Complex_Selector_Ptr pNewSelector = pair.second; - std::cerr << "[" << *pSels << "], "; - printComplexSelector(pNewSelector, NULL, false); - } - std::cerr << "]"; - - if (newline) { - std::cerr << std::endl; - } - } - - // Print a string representation of a ComplexSelectorSet - static void printSourcesSet(ComplexSelectorSet& sources, const char* message=NULL, bool newline=true) { - - if (message) { - std::cerr << message; - } - - // Convert to a deque of strings so we can sort since order doesn't matter in a set. This should cut down on - // the differences we see when debug printing. - typedef std::deque SourceStrings; - SourceStrings sourceStrings; - for (ComplexSelectorSet::iterator iterator = sources.begin(), iteratorEnd = sources.end(); iterator != iteratorEnd; ++iterator) { - Complex_Selector_Ptr pSource = *iterator; - std::stringstream sstream; - sstream << complexSelectorToNode(pSource); - sourceStrings.push_back(sstream.str()); - } - - // Sort to get consistent output - std::sort(sourceStrings.begin(), sourceStrings.end()); - - std::cerr << "ComplexSelectorSet["; - for (SourceStrings::iterator iterator = sourceStrings.begin(), iteratorEnd = sourceStrings.end(); iterator != iteratorEnd; ++iterator) { - std::string source = *iterator; - if (iterator != sourceStrings.begin()) { - std::cerr << ", "; - } - std::cerr << source; - } - std::cerr << "]"; - - if (newline) { - std::cerr << std::endl; - } - } - - - std::ostream& operator<<(std::ostream& os, SubSetMapPairs& entries) { - os << "SUBSET_MAP_ENTRIES["; - - for (SubSetMapPairs::iterator iterator = entries.begin(), endIterator = entries.end(); iterator != endIterator; ++iterator) { - Complex_Selector_Obj pExtComplexSelector = iterator->first; // The selector up to where the @extend is (ie, the thing to merge) - Compound_Selector_Obj pExtCompoundSelector = iterator->second; // The stuff after the @extend - - if (iterator != entries.begin()) { - os << ", "; - } - - os << "("; - - if (pExtComplexSelector) { - std::cerr << *pExtComplexSelector; - } else { - std::cerr << "NULL"; - } - - os << " -> "; - - if (pExtCompoundSelector) { - std::cerr << *pExtCompoundSelector; - } else { - std::cerr << "NULL"; - } - - os << ")"; - - } - - os << "]"; - - return os; - } -#endif - - static bool parentSuperselector(Complex_Selector_Ptr pOne, Complex_Selector_Ptr pTwo) { - // TODO: figure out a better way to create a Complex_Selector from scratch - // TODO: There's got to be a better way. This got ugly quick... - Element_Selector_Obj fakeParent = SASS_MEMORY_NEW(Element_Selector, ParserState("[FAKE]"), "temp"); - Compound_Selector_Obj fakeHead = SASS_MEMORY_NEW(Compound_Selector, ParserState("[FAKE]"), 1 /*size*/); - fakeHead->elements().push_back(fakeParent); - Complex_Selector_Obj fakeParentContainer = SASS_MEMORY_NEW(Complex_Selector, ParserState("[FAKE]"), Complex_Selector::ANCESTOR_OF, fakeHead /*head*/, NULL /*tail*/); - - pOne->set_innermost(fakeParentContainer, Complex_Selector::ANCESTOR_OF); - pTwo->set_innermost(fakeParentContainer, Complex_Selector::ANCESTOR_OF); - - bool isSuperselector = pOne->is_superselector_of(pTwo); - - pOne->clear_innermost(); - pTwo->clear_innermost(); - - return isSuperselector; - } - - void nodeToComplexSelectorDeque(const Node& node, ComplexSelectorDeque& out) { - for (NodeDeque::iterator iter = node.collection()->begin(), iterEnd = node.collection()->end(); iter != iterEnd; iter++) { - Node& child = *iter; - out.push_back(nodeToComplexSelector(child)); - } - } - - Node complexSelectorDequeToNode(const ComplexSelectorDeque& deque) { - Node result = Node::createCollection(); - - for (ComplexSelectorDeque::const_iterator iter = deque.begin(), iterEnd = deque.end(); iter != iterEnd; iter++) { - Complex_Selector_Obj pChild = *iter; - result.collection()->push_back(complexSelectorToNode(pChild)); - } - - return result; - } - - class LcsCollectionComparator { - public: - LcsCollectionComparator() {} - - bool operator()(Complex_Selector_Obj pOne, Complex_Selector_Obj pTwo, Complex_Selector_Obj& pOut) const { - /* - This code is based on the following block from ruby sass' subweave - do |s1, s2| - next s1 if s1 == s2 - next unless s1.first.is_a?(SimpleSequence) && s2.first.is_a?(SimpleSequence) - next s2 if parent_superselector?(s1, s2) - next s1 if parent_superselector?(s2, s1) - end - */ - - if (*pOne == *pTwo) { - pOut = pOne; - return true; - } - - if (pOne->combinator() != Complex_Selector::ANCESTOR_OF || pTwo->combinator() != Complex_Selector::ANCESTOR_OF) { - return false; - } - - if (parentSuperselector(pOne, pTwo)) { - pOut = pTwo; - return true; - } - - if (parentSuperselector(pTwo, pOne)) { - pOut = pOne; - return true; - } - - return false; - } - }; - - - /* - This is the equivalent of ruby's Sass::Util.lcs_backtrace. - - # Computes a single longest common subsequence for arrays x and y. - # Algorithm from http://en.wikipedia.org/wiki/Longest_common_subsequence_problem#Reading_out_an_LCS - */ - void lcs_backtrace(const LCSTable& c, ComplexSelectorDeque& x, ComplexSelectorDeque& y, int i, int j, const LcsCollectionComparator& comparator, ComplexSelectorDeque& out) { - //DEBUG_PRINTLN(LCS, "LCSBACK: X=" << x << " Y=" << y << " I=" << i << " J=" << j) - // TODO: make printComplexSelectorDeque and use DEBUG_EXEC AND DEBUG_PRINTLN HERE to get equivalent output - - if (i == 0 || j == 0) { - DEBUG_PRINTLN(LCS, "RETURNING EMPTY") - return; - } - - - Complex_Selector_Obj pCompareOut; - if (comparator(x[i], y[j], pCompareOut)) { - DEBUG_PRINTLN(LCS, "RETURNING AFTER ELEM COMPARE") - lcs_backtrace(c, x, y, i - 1, j - 1, comparator, out); - out.push_back(pCompareOut); - return; - } - - if (c[i][j - 1] > c[i - 1][j]) { - DEBUG_PRINTLN(LCS, "RETURNING AFTER TABLE COMPARE") - lcs_backtrace(c, x, y, i, j - 1, comparator, out); - return; - } - - DEBUG_PRINTLN(LCS, "FINAL RETURN") - lcs_backtrace(c, x, y, i - 1, j, comparator, out); - return; - } - - /* - This is the equivalent of ruby's Sass::Util.lcs_table. - - # Calculates the memoization table for the Least Common Subsequence algorithm. - # Algorithm from http://en.wikipedia.org/wiki/Longest_common_subsequence_problem#Computing_the_length_of_the_LCS - */ - void lcs_table(const ComplexSelectorDeque& x, const ComplexSelectorDeque& y, const LcsCollectionComparator& comparator, LCSTable& out) { - //DEBUG_PRINTLN(LCS, "LCSTABLE: X=" << x << " Y=" << y) - // TODO: make printComplexSelectorDeque and use DEBUG_EXEC AND DEBUG_PRINTLN HERE to get equivalent output - - LCSTable c(x.size(), std::vector(y.size())); - - // These shouldn't be necessary since the vector will be initialized to 0 already. - // x.size.times {|i| c[i][0] = 0} - // y.size.times {|j| c[0][j] = 0} - - for (size_t i = 1; i < x.size(); i++) { - for (size_t j = 1; j < y.size(); j++) { - Complex_Selector_Obj pCompareOut; - - if (comparator(x[i], y[j], pCompareOut)) { - c[i][j] = c[i - 1][j - 1] + 1; - } else { - c[i][j] = std::max(c[i][j - 1], c[i - 1][j]); - } - } - } - - out = c; - } - - /* - This is the equivalent of ruby's Sass::Util.lcs. - - # Computes a single longest common subsequence for `x` and `y`. - # If there are more than one longest common subsequences, - # the one returned is that which starts first in `x`. - - # @param x [NodeCollection] - # @param y [NodeCollection] - # @comparator An equality check between elements of `x` and `y`. - # @return [NodeCollection] The LCS - - http://en.wikipedia.org/wiki/Longest_common_subsequence_problem - */ - void lcs(ComplexSelectorDeque& x, ComplexSelectorDeque& y, const LcsCollectionComparator& comparator, ComplexSelectorDeque& out) { - //DEBUG_PRINTLN(LCS, "LCS: X=" << x << " Y=" << y) - // TODO: make printComplexSelectorDeque and use DEBUG_EXEC AND DEBUG_PRINTLN HERE to get equivalent output - - x.push_front(NULL); - y.push_front(NULL); - - LCSTable table; - lcs_table(x, y, comparator, table); - - return lcs_backtrace(table, x, y, static_cast(x.size()) - 1, static_cast(y.size()) - 1, comparator, out); - } - - - /* - This is the equivalent of ruby's Sequence.trim. - - The following is the modified version of the ruby code that was more portable to C++. You - should be able to drop it into ruby 3.2.19 and get the same results from ruby sass. - - # Avoid truly horrific quadratic behavior. TODO: I think there - # may be a way to get perfect trimming without going quadratic. - return seqses if seqses.size > 100 - - # Keep the results in a separate array so we can be sure we aren't - # comparing against an already-trimmed selector. This ensures that two - # identical selectors don't mutually trim one another. - result = seqses.dup - - # This is n^2 on the sequences, but only comparing between - # separate sequences should limit the quadratic behavior. - seqses.each_with_index do |seqs1, i| - tempResult = [] - - for seq1 in seqs1 do - max_spec = 0 - for seq in _sources(seq1) do - max_spec = [max_spec, seq.specificity].max - end - - - isMoreSpecificOuter = false - for seqs2 in result do - if seqs1.equal?(seqs2) then - next - end - - # Second Law of Extend: the specificity of a generated selector - # should never be less than the specificity of the extending - # selector. - # - # See https://github.com/nex3/sass/issues/324. - isMoreSpecificInner = false - for seq2 in seqs2 do - isMoreSpecificInner = _specificity(seq2) >= max_spec && _superselector?(seq2, seq1) - if isMoreSpecificInner then - break - end - end - - if isMoreSpecificInner then - isMoreSpecificOuter = true - break - end - end - - if !isMoreSpecificOuter then - tempResult.push(seq1) - end - end - - result[i] = tempResult - - end - - result - */ - /* - - IMPROVEMENT: We could probably work directly in the output trimmed deque. - */ - Node Extend::trim(Node& seqses, bool isReplace) { - // See the comments in the above ruby code before embarking on understanding this function. - - // Avoid poor performance in extreme cases. - if (seqses.collection()->size() > 100) { - return seqses; - } - - - DEBUG_PRINTLN(TRIM, "TRIM: " << seqses) - - - Node result = Node::createCollection(); - result.plus(seqses); - - DEBUG_PRINTLN(TRIM, "RESULT INITIAL: " << result) - - // Normally we use the standard STL iterators, but in this case, we need to access the result collection by index since we're - // iterating the input collection, computing a value, and then setting the result in the output collection. We have to keep track - // of the index manually. - int toTrimIndex = 0; - - for (NodeDeque::iterator seqsesIter = seqses.collection()->begin(), seqsesIterEnd = seqses.collection()->end(); seqsesIter != seqsesIterEnd; ++seqsesIter) { - Node& seqs1 = *seqsesIter; - - DEBUG_PRINTLN(TRIM, "SEQS1: " << seqs1 << " " << toTrimIndex) - - Node tempResult = Node::createCollection(); - tempResult.got_line_feed = seqs1.got_line_feed; - - for (NodeDeque::iterator seqs1Iter = seqs1.collection()->begin(), seqs1EndIter = seqs1.collection()->end(); seqs1Iter != seqs1EndIter; ++seqs1Iter) { - Node& seq1 = *seqs1Iter; - - Complex_Selector_Obj pSeq1 = nodeToComplexSelector(seq1); - - // Compute the maximum specificity. This requires looking at the "sources" of the sequence. See SimpleSequence.sources in the ruby code - // for a good description of sources. - // - // TODO: I'm pretty sure there's a bug in the sources code. It was implemented for sass-spec's 182_test_nested_extend_loop test. - // While the test passes, I compared the state of each trim call to verify correctness. The last trim call had incorrect sources. We - // had an extra source that the ruby version did not have. Without a failing test case, this is going to be extra hard to find. My - // best guess at this point is that we're cloning an object somewhere and maintaining the sources when we shouldn't be. This is purely - // a guess though. - unsigned long maxSpecificity = isReplace ? pSeq1->specificity() : 0; - ComplexSelectorSet sources = pSeq1->sources(); - - DEBUG_PRINTLN(TRIM, "TRIM SEQ1: " << seq1) - DEBUG_EXEC(TRIM, printSourcesSet(sources, "TRIM SOURCES: ")) - - for (ComplexSelectorSet::iterator sourcesSetIterator = sources.begin(), sourcesSetIteratorEnd = sources.end(); sourcesSetIterator != sourcesSetIteratorEnd; ++sourcesSetIterator) { - const Complex_Selector_Obj& pCurrentSelector = *sourcesSetIterator; - maxSpecificity = std::max(maxSpecificity, pCurrentSelector->specificity()); - } - - DEBUG_PRINTLN(TRIM, "MAX SPECIFICITY: " << maxSpecificity) - - bool isMoreSpecificOuter = false; - - int resultIndex = 0; - - for (NodeDeque::iterator resultIter = result.collection()->begin(), resultIterEnd = result.collection()->end(); resultIter != resultIterEnd; ++resultIter) { - Node& seqs2 = *resultIter; - - DEBUG_PRINTLN(TRIM, "SEQS1: " << seqs1) - DEBUG_PRINTLN(TRIM, "SEQS2: " << seqs2) - - // Do not compare the same sequence to itself. The ruby call we're trying to - // emulate is: seqs1.equal?(seqs2). equal? is an object comparison, not an equivalency comparision. - // Since we have the same pointers in seqes and results, we can do a pointer comparision. seqs1 is - // derived from seqses and seqs2 is derived from result. - if (seqs1.collection() == seqs2.collection()) { - DEBUG_PRINTLN(TRIM, "CONTINUE") - continue; - } - - bool isMoreSpecificInner = false; - - for (NodeDeque::iterator seqs2Iter = seqs2.collection()->begin(), seqs2IterEnd = seqs2.collection()->end(); seqs2Iter != seqs2IterEnd; ++seqs2Iter) { - Node& seq2 = *seqs2Iter; - - Complex_Selector_Obj pSeq2 = nodeToComplexSelector(seq2); - - DEBUG_PRINTLN(TRIM, "SEQ2 SPEC: " << pSeq2->specificity()) - DEBUG_PRINTLN(TRIM, "IS SPEC: " << pSeq2->specificity() << " >= " << maxSpecificity << " " << (pSeq2->specificity() >= maxSpecificity ? "true" : "false")) - DEBUG_PRINTLN(TRIM, "IS SUPER: " << (pSeq2->is_superselector_of(pSeq1) ? "true" : "false")) - - isMoreSpecificInner = pSeq2->specificity() >= maxSpecificity && pSeq2->is_superselector_of(pSeq1); - - if (isMoreSpecificInner) { - DEBUG_PRINTLN(TRIM, "FOUND MORE SPECIFIC") - break; - } - } - - // If we found something more specific, we're done. Let the outer loop know and stop iterating. - if (isMoreSpecificInner) { - isMoreSpecificOuter = true; - break; - } - - resultIndex++; - } - - if (!isMoreSpecificOuter) { - DEBUG_PRINTLN(TRIM, "PUSHING: " << seq1) - tempResult.collection()->push_back(seq1); - } - - } - - DEBUG_PRINTLN(TRIM, "RESULT BEFORE ASSIGN: " << result) - DEBUG_PRINTLN(TRIM, "TEMP RESULT: " << toTrimIndex << " " << tempResult) - (*result.collection())[toTrimIndex] = tempResult; - - toTrimIndex++; - - DEBUG_PRINTLN(TRIM, "RESULT: " << result) - } - - return result; - } - - - - static bool parentSuperselector(const Node& one, const Node& two) { - // TODO: figure out a better way to create a Complex_Selector from scratch - // TODO: There's got to be a better way. This got ugly quick... - Element_Selector_Obj fakeParent = SASS_MEMORY_NEW(Element_Selector, ParserState("[FAKE]"), "temp"); - Compound_Selector_Obj fakeHead = SASS_MEMORY_NEW(Compound_Selector, ParserState("[FAKE]"), 1 /*size*/); - fakeHead->elements().push_back(fakeParent); - Complex_Selector_Obj fakeParentContainer = SASS_MEMORY_NEW(Complex_Selector, ParserState("[FAKE]"), Complex_Selector::ANCESTOR_OF, fakeHead /*head*/, NULL /*tail*/); - - Complex_Selector_Obj pOneWithFakeParent = nodeToComplexSelector(one); - pOneWithFakeParent->set_innermost(fakeParentContainer, Complex_Selector::ANCESTOR_OF); - Complex_Selector_Obj pTwoWithFakeParent = nodeToComplexSelector(two); - pTwoWithFakeParent->set_innermost(fakeParentContainer, Complex_Selector::ANCESTOR_OF); - - return pOneWithFakeParent->is_superselector_of(pTwoWithFakeParent); - } - - - class ParentSuperselectorChunker { - public: - ParentSuperselectorChunker(Node& lcs) : mLcs(lcs) {} - Node& mLcs; - - bool operator()(const Node& seq) const { - // {|s| parent_superselector?(s.first, lcs.first)} - if (seq.collection()->size() == 0) return false; - return parentSuperselector(seq.collection()->front(), mLcs.collection()->front()); - } - }; - - class SubweaveEmptyChunker { - public: - bool operator()(const Node& seq) const { - // {|s| s.empty?} - - return seq.collection()->empty(); - } - }; - - /* - # Takes initial subsequences of `seq1` and `seq2` and returns all - # orderings of those subsequences. The initial subsequences are determined - # by a block. - # - # Destructively removes the initial subsequences of `seq1` and `seq2`. - # - # For example, given `(A B C | D E)` and `(1 2 | 3 4 5)` (with `|` - # denoting the boundary of the initial subsequence), this would return - # `[(A B C 1 2), (1 2 A B C)]`. The sequences would then be `(D E)` and - # `(3 4 5)`. - # - # @param seq1 [Array] - # @param seq2 [Array] - # @yield [a] Used to determine when to cut off the initial subsequences. - # Called repeatedly for each sequence until it returns true. - # @yieldparam a [Array] A final subsequence of one input sequence after - # cutting off some initial subsequence. - # @yieldreturn [Boolean] Whether or not to cut off the initial subsequence - # here. - # @return [Array] All possible orderings of the initial subsequences. - def chunks(seq1, seq2) - chunk1 = [] - chunk1 << seq1.shift until yield seq1 - chunk2 = [] - chunk2 << seq2.shift until yield seq2 - return [] if chunk1.empty? && chunk2.empty? - return [chunk2] if chunk1.empty? - return [chunk1] if chunk2.empty? - [chunk1 + chunk2, chunk2 + chunk1] - end - */ - template - static Node chunks(Node& seq1, Node& seq2, const ChunkerType& chunker) { - Node chunk1 = Node::createCollection(); - while (seq1.collection()->size() && !chunker(seq1)) { - chunk1.collection()->push_back(seq1.collection()->front()); - seq1.collection()->pop_front(); - } - - Node chunk2 = Node::createCollection(); - while (!seq2.collection()->empty() && !chunker(seq2)) { - chunk2.collection()->push_back(seq2.collection()->front()); - seq2.collection()->pop_front(); - } - - if (chunk1.collection()->empty() && chunk2.collection()->empty()) { - DEBUG_PRINTLN(CHUNKS, "RETURNING BOTH EMPTY") - return Node::createCollection(); - } - - if (chunk1.collection()->empty()) { - Node chunk2Wrapper = Node::createCollection(); - chunk2Wrapper.collection()->push_back(chunk2); - DEBUG_PRINTLN(CHUNKS, "RETURNING ONE EMPTY") - return chunk2Wrapper; - } - - if (chunk2.collection()->empty()) { - Node chunk1Wrapper = Node::createCollection(); - chunk1Wrapper.collection()->push_back(chunk1); - DEBUG_PRINTLN(CHUNKS, "RETURNING TWO EMPTY") - return chunk1Wrapper; - } - - Node perms = Node::createCollection(); - - Node firstPermutation = Node::createCollection(); - firstPermutation.collection()->insert(firstPermutation.collection()->end(), chunk1.collection()->begin(), chunk1.collection()->end()); - firstPermutation.collection()->insert(firstPermutation.collection()->end(), chunk2.collection()->begin(), chunk2.collection()->end()); - perms.collection()->push_back(firstPermutation); - - Node secondPermutation = Node::createCollection(); - secondPermutation.collection()->insert(secondPermutation.collection()->end(), chunk2.collection()->begin(), chunk2.collection()->end()); - secondPermutation.collection()->insert(secondPermutation.collection()->end(), chunk1.collection()->begin(), chunk1.collection()->end()); - perms.collection()->push_back(secondPermutation); - - DEBUG_PRINTLN(CHUNKS, "RETURNING PERM") - - return perms; - } - - - static Node groupSelectors(Node& seq) { - Node newSeq = Node::createCollection(); - - Node tail = Node::createCollection(); - tail.plus(seq); - - while (!tail.collection()->empty()) { - Node head = Node::createCollection(); - - do { - head.collection()->push_back(tail.collection()->front()); - tail.collection()->pop_front(); - } while (!tail.collection()->empty() && (head.collection()->back().isCombinator() || tail.collection()->front().isCombinator())); - - newSeq.collection()->push_back(head); - } - - return newSeq; - } - - - static void getAndRemoveInitialOps(Node& seq, Node& ops) { - NodeDeque& seqCollection = *(seq.collection()); - NodeDeque& opsCollection = *(ops.collection()); - - while (seqCollection.size() > 0 && seqCollection.front().isCombinator()) { - opsCollection.push_back(seqCollection.front()); - seqCollection.pop_front(); - } - } - - - static void getAndRemoveFinalOps(Node& seq, Node& ops) { - NodeDeque& seqCollection = *(seq.collection()); - NodeDeque& opsCollection = *(ops.collection()); - - while (seqCollection.size() > 0 && seqCollection.back().isCombinator()) { - opsCollection.push_back(seqCollection.back()); // Purposefully reversed to match ruby code - seqCollection.pop_back(); - } - } - - - /* - def merge_initial_ops(seq1, seq2) - ops1, ops2 = [], [] - ops1 << seq1.shift while seq1.first.is_a?(String) - ops2 << seq2.shift while seq2.first.is_a?(String) - - newline = false - newline ||= !!ops1.shift if ops1.first == "\n" - newline ||= !!ops2.shift if ops2.first == "\n" - - # If neither sequence is a subsequence of the other, they cannot be - # merged successfully - lcs = Sass::Util.lcs(ops1, ops2) - return unless lcs == ops1 || lcs == ops2 - return (newline ? ["\n"] : []) + (ops1.size > ops2.size ? ops1 : ops2) - end - */ - static Node mergeInitialOps(Node& seq1, Node& seq2) { - Node ops1 = Node::createCollection(); - Node ops2 = Node::createCollection(); - - getAndRemoveInitialOps(seq1, ops1); - getAndRemoveInitialOps(seq2, ops2); - - // TODO: Do we have this information available to us? - // newline = false - // newline ||= !!ops1.shift if ops1.first == "\n" - // newline ||= !!ops2.shift if ops2.first == "\n" - - // If neither sequence is a subsequence of the other, they cannot be merged successfully - DefaultLcsComparator lcsDefaultComparator; - Node opsLcs = lcs(ops1, ops2, lcsDefaultComparator); - - if (!(opsLcs == ops1 || opsLcs == ops2)) { - return Node::createNil(); - } - - // TODO: more newline logic - // return (newline ? ["\n"] : []) + (ops1.size > ops2.size ? ops1 : ops2) - - return (ops1.collection()->size() > ops2.collection()->size() ? ops1 : ops2); - } - - - /* - def merge_final_ops(seq1, seq2, res = []) - - - # This code looks complicated, but it's actually just a bunch of special - # cases for interactions between different combinators. - op1, op2 = ops1.first, ops2.first - if op1 && op2 - sel1 = seq1.pop - sel2 = seq2.pop - if op1 == '~' && op2 == '~' - if sel1.superselector?(sel2) - res.unshift sel2, '~' - elsif sel2.superselector?(sel1) - res.unshift sel1, '~' - else - merged = sel1.unify(sel2.members, sel2.subject?) - res.unshift [ - [sel1, '~', sel2, '~'], - [sel2, '~', sel1, '~'], - ([merged, '~'] if merged) - ].compact - end - elsif (op1 == '~' && op2 == '+') || (op1 == '+' && op2 == '~') - if op1 == '~' - tilde_sel, plus_sel = sel1, sel2 - else - tilde_sel, plus_sel = sel2, sel1 - end - - if tilde_sel.superselector?(plus_sel) - res.unshift plus_sel, '+' - else - merged = plus_sel.unify(tilde_sel.members, tilde_sel.subject?) - res.unshift [ - [tilde_sel, '~', plus_sel, '+'], - ([merged, '+'] if merged) - ].compact - end - elsif op1 == '>' && %w[~ +].include?(op2) - res.unshift sel2, op2 - seq1.push sel1, op1 - elsif op2 == '>' && %w[~ +].include?(op1) - res.unshift sel1, op1 - seq2.push sel2, op2 - elsif op1 == op2 - return unless merged = sel1.unify(sel2.members, sel2.subject?) - res.unshift merged, op1 - else - # Unknown selector combinators can't be unified - return - end - return merge_final_ops(seq1, seq2, res) - elsif op1 - seq2.pop if op1 == '>' && seq2.last && seq2.last.superselector?(seq1.last) - res.unshift seq1.pop, op1 - return merge_final_ops(seq1, seq2, res) - else # op2 - seq1.pop if op2 == '>' && seq1.last && seq1.last.superselector?(seq2.last) - res.unshift seq2.pop, op2 - return merge_final_ops(seq1, seq2, res) - end - end - */ - static Node mergeFinalOps(Node& seq1, Node& seq2, Node& res) { - - Node ops1 = Node::createCollection(); - Node ops2 = Node::createCollection(); - - getAndRemoveFinalOps(seq1, ops1); - getAndRemoveFinalOps(seq2, ops2); - - // TODO: do we have newlines to remove? - // ops1.reject! {|o| o == "\n"} - // ops2.reject! {|o| o == "\n"} - - if (ops1.collection()->empty() && ops2.collection()->empty()) { - return res; - } - - if (ops1.collection()->size() > 1 || ops2.collection()->size() > 1) { - DefaultLcsComparator lcsDefaultComparator; - Node opsLcs = lcs(ops1, ops2, lcsDefaultComparator); - - // If there are multiple operators, something hacky's going on. If one is a supersequence of the other, use that, otherwise give up. - - if (!(opsLcs == ops1 || opsLcs == ops2)) { - return Node::createNil(); - } - - if (ops1.collection()->size() > ops2.collection()->size()) { - res.collection()->insert(res.collection()->begin(), ops1.collection()->rbegin(), ops1.collection()->rend()); - } else { - res.collection()->insert(res.collection()->begin(), ops2.collection()->rbegin(), ops2.collection()->rend()); - } - - return res; - } - - if (!ops1.collection()->empty() && !ops2.collection()->empty()) { - - Node op1 = ops1.collection()->front(); - Node op2 = ops2.collection()->front(); - - Node sel1 = seq1.collection()->back(); - seq1.collection()->pop_back(); - - Node sel2 = seq2.collection()->back(); - seq2.collection()->pop_back(); - - if (op1.combinator() == Complex_Selector::PRECEDES && op2.combinator() == Complex_Selector::PRECEDES) { - - if (sel1.selector()->is_superselector_of(sel2.selector())) { - - res.collection()->push_front(op1 /*PRECEDES - could have been op2 as well*/); - res.collection()->push_front(sel2); - - } else if (sel2.selector()->is_superselector_of(sel1.selector())) { - - res.collection()->push_front(op1 /*PRECEDES - could have been op2 as well*/); - res.collection()->push_front(sel1); - - } else { - - DEBUG_PRINTLN(ALL, "sel1: " << sel1) - DEBUG_PRINTLN(ALL, "sel2: " << sel2) - - Complex_Selector_Obj pMergedWrapper = SASS_MEMORY_CLONE(sel1.selector()); // Clone the Complex_Selector to get back to something we can transform to a node once we replace the head with the unification result - // TODO: does subject matter? Ruby: return unless merged = sel1.unify(sel2.members, sel2.subject?) - Compound_Selector_Ptr pMerged = sel1.selector()->head()->unify_with(sel2.selector()->head()); - pMergedWrapper->head(pMerged); - - DEBUG_EXEC(ALL, printCompoundSelector(pMerged, "MERGED: ")) - - Node newRes = Node::createCollection(); - - Node firstPerm = Node::createCollection(); - firstPerm.collection()->push_back(sel1); - firstPerm.collection()->push_back(Node::createCombinator(Complex_Selector::PRECEDES)); - firstPerm.collection()->push_back(sel2); - firstPerm.collection()->push_back(Node::createCombinator(Complex_Selector::PRECEDES)); - newRes.collection()->push_back(firstPerm); - - Node secondPerm = Node::createCollection(); - secondPerm.collection()->push_back(sel2); - secondPerm.collection()->push_back(Node::createCombinator(Complex_Selector::PRECEDES)); - secondPerm.collection()->push_back(sel1); - secondPerm.collection()->push_back(Node::createCombinator(Complex_Selector::PRECEDES)); - newRes.collection()->push_back(secondPerm); - - if (pMerged) { - Node mergedPerm = Node::createCollection(); - mergedPerm.collection()->push_back(Node::createSelector(pMergedWrapper)); - mergedPerm.collection()->push_back(Node::createCombinator(Complex_Selector::PRECEDES)); - newRes.collection()->push_back(mergedPerm); - } - - res.collection()->push_front(newRes); - - DEBUG_PRINTLN(ALL, "RESULT: " << res) - - } - - } else if (((op1.combinator() == Complex_Selector::PRECEDES && op2.combinator() == Complex_Selector::ADJACENT_TO)) || ((op1.combinator() == Complex_Selector::ADJACENT_TO && op2.combinator() == Complex_Selector::PRECEDES))) { - - Node tildeSel = sel1; - Node plusSel = sel2; - Node plusOp = op2; - if (op1.combinator() != Complex_Selector::PRECEDES) { - tildeSel = sel2; - plusSel = sel1; - plusOp = op1; - } - - if (tildeSel.selector()->is_superselector_of(plusSel.selector())) { - - res.collection()->push_front(plusOp); - res.collection()->push_front(plusSel); - - } else { - - DEBUG_PRINTLN(ALL, "PLUS SEL: " << plusSel) - DEBUG_PRINTLN(ALL, "TILDE SEL: " << tildeSel) - - Complex_Selector_Obj pMergedWrapper = SASS_MEMORY_CLONE(plusSel.selector()); // Clone the Complex_Selector to get back to something we can transform to a node once we replace the head with the unification result - // TODO: does subject matter? Ruby: merged = plus_sel.unify(tilde_sel.members, tilde_sel.subject?) - Compound_Selector_Ptr pMerged = plusSel.selector()->head()->unify_with(tildeSel.selector()->head()); - pMergedWrapper->head(pMerged); - - DEBUG_EXEC(ALL, printCompoundSelector(pMerged, "MERGED: ")) - - Node newRes = Node::createCollection(); - - Node firstPerm = Node::createCollection(); - firstPerm.collection()->push_back(tildeSel); - firstPerm.collection()->push_back(Node::createCombinator(Complex_Selector::PRECEDES)); - firstPerm.collection()->push_back(plusSel); - firstPerm.collection()->push_back(Node::createCombinator(Complex_Selector::ADJACENT_TO)); - newRes.collection()->push_back(firstPerm); - - if (pMerged) { - Node mergedPerm = Node::createCollection(); - mergedPerm.collection()->push_back(Node::createSelector(pMergedWrapper)); - mergedPerm.collection()->push_back(Node::createCombinator(Complex_Selector::ADJACENT_TO)); - newRes.collection()->push_back(mergedPerm); - } - - res.collection()->push_front(newRes); - - DEBUG_PRINTLN(ALL, "RESULT: " << res) - - } - } else if (op1.combinator() == Complex_Selector::PARENT_OF && (op2.combinator() == Complex_Selector::PRECEDES || op2.combinator() == Complex_Selector::ADJACENT_TO)) { - - res.collection()->push_front(op2); - res.collection()->push_front(sel2); - - seq1.collection()->push_back(sel1); - seq1.collection()->push_back(op1); - - } else if (op2.combinator() == Complex_Selector::PARENT_OF && (op1.combinator() == Complex_Selector::PRECEDES || op1.combinator() == Complex_Selector::ADJACENT_TO)) { - - res.collection()->push_front(op1); - res.collection()->push_front(sel1); - - seq2.collection()->push_back(sel2); - seq2.collection()->push_back(op2); - - } else if (op1.combinator() == op2.combinator()) { - - DEBUG_PRINTLN(ALL, "sel1: " << sel1) - DEBUG_PRINTLN(ALL, "sel2: " << sel2) - - Complex_Selector_Obj pMergedWrapper = SASS_MEMORY_CLONE(sel1.selector()); // Clone the Complex_Selector to get back to something we can transform to a node once we replace the head with the unification result - // TODO: does subject matter? Ruby: return unless merged = sel1.unify(sel2.members, sel2.subject?) - Compound_Selector_Ptr pMerged = sel1.selector()->head()->unify_with(sel2.selector()->head()); - pMergedWrapper->head(pMerged); - - DEBUG_EXEC(ALL, printCompoundSelector(pMerged, "MERGED: ")) - - if (!pMerged) { - return Node::createNil(); - } - - res.collection()->push_front(op1); - res.collection()->push_front(Node::createSelector(pMergedWrapper)); - - DEBUG_PRINTLN(ALL, "RESULT: " << res) - - } else { - return Node::createNil(); - } - - return mergeFinalOps(seq1, seq2, res); - - } else if (!ops1.collection()->empty()) { - - Node op1 = ops1.collection()->front(); - - if (op1.combinator() == Complex_Selector::PARENT_OF && !seq2.collection()->empty() && seq2.collection()->back().selector()->is_superselector_of(seq1.collection()->back().selector())) { - seq2.collection()->pop_back(); - } - - // TODO: consider unshift(NodeCollection, Node) - res.collection()->push_front(op1); - res.collection()->push_front(seq1.collection()->back()); - seq1.collection()->pop_back(); - - return mergeFinalOps(seq1, seq2, res); - - } else { // !ops2.collection()->empty() - - Node op2 = ops2.collection()->front(); - - if (op2.combinator() == Complex_Selector::PARENT_OF && !seq1.collection()->empty() && seq1.collection()->back().selector()->is_superselector_of(seq2.collection()->back().selector())) { - seq1.collection()->pop_back(); - } - - res.collection()->push_front(op2); - res.collection()->push_front(seq2.collection()->back()); - seq2.collection()->pop_back(); - - return mergeFinalOps(seq1, seq2, res); - - } - - } - - - /* - This is the equivalent of ruby's Sequence.subweave. - - Here is the original subweave code for reference during porting. - - def subweave(seq1, seq2) - return [seq2] if seq1.empty? - return [seq1] if seq2.empty? - - seq1, seq2 = seq1.dup, seq2.dup - return unless init = merge_initial_ops(seq1, seq2) - return unless fin = merge_final_ops(seq1, seq2) - seq1 = group_selectors(seq1) - seq2 = group_selectors(seq2) - lcs = Sass::Util.lcs(seq2, seq1) do |s1, s2| - next s1 if s1 == s2 - next unless s1.first.is_a?(SimpleSequence) && s2.first.is_a?(SimpleSequence) - next s2 if parent_superselector?(s1, s2) - next s1 if parent_superselector?(s2, s1) - end - - diff = [[init]] - until lcs.empty? - diff << chunks(seq1, seq2) {|s| parent_superselector?(s.first, lcs.first)} << [lcs.shift] - seq1.shift - seq2.shift - end - diff << chunks(seq1, seq2) {|s| s.empty?} - diff += fin.map {|sel| sel.is_a?(Array) ? sel : [sel]} - diff.reject! {|c| c.empty?} - - result = Sass::Util.paths(diff).map {|p| p.flatten}.reject {|p| path_has_two_subjects?(p)} - - result - end - */ - Node subweave(Node& one, Node& two) { - // Check for the simple cases - if (one.collection()->size() == 0) { - Node out = Node::createCollection(); - out.collection()->push_back(two); - return out; - } - if (two.collection()->size() == 0) { - Node out = Node::createCollection(); - out.collection()->push_back(one); - return out; - } - - Node seq1 = Node::createCollection(); - seq1.plus(one); - Node seq2 = Node::createCollection(); - seq2.plus(two); - - DEBUG_PRINTLN(SUBWEAVE, "SUBWEAVE ONE: " << seq1) - DEBUG_PRINTLN(SUBWEAVE, "SUBWEAVE TWO: " << seq2) - - Node init = mergeInitialOps(seq1, seq2); - if (init.isNil()) { - return Node::createNil(); - } - - DEBUG_PRINTLN(SUBWEAVE, "INIT: " << init) - - Node res = Node::createCollection(); - Node fin = mergeFinalOps(seq1, seq2, res); - if (fin.isNil()) { - return Node::createNil(); - } - - DEBUG_PRINTLN(SUBWEAVE, "FIN: " << fin) - - - // Moving this line up since fin isn't modified between now and when it happened before - // fin.map {|sel| sel.is_a?(Array) ? sel : [sel]} - - for (NodeDeque::iterator finIter = fin.collection()->begin(), finEndIter = fin.collection()->end(); - finIter != finEndIter; ++finIter) { - - Node& childNode = *finIter; - - if (!childNode.isCollection()) { - Node wrapper = Node::createCollection(); - wrapper.collection()->push_back(childNode); - childNode = wrapper; - } - - } - - DEBUG_PRINTLN(SUBWEAVE, "FIN MAPPED: " << fin) - - - - Node groupSeq1 = groupSelectors(seq1); - DEBUG_PRINTLN(SUBWEAVE, "SEQ1: " << groupSeq1) - - Node groupSeq2 = groupSelectors(seq2); - DEBUG_PRINTLN(SUBWEAVE, "SEQ2: " << groupSeq2) - - - ComplexSelectorDeque groupSeq1Converted; - nodeToComplexSelectorDeque(groupSeq1, groupSeq1Converted); - - ComplexSelectorDeque groupSeq2Converted; - nodeToComplexSelectorDeque(groupSeq2, groupSeq2Converted); - - ComplexSelectorDeque out; - LcsCollectionComparator collectionComparator; - lcs(groupSeq2Converted, groupSeq1Converted, collectionComparator, out); - Node seqLcs = complexSelectorDequeToNode(out); - - DEBUG_PRINTLN(SUBWEAVE, "SEQLCS: " << seqLcs) - - - Node initWrapper = Node::createCollection(); - initWrapper.collection()->push_back(init); - Node diff = Node::createCollection(); - diff.collection()->push_back(initWrapper); - - DEBUG_PRINTLN(SUBWEAVE, "DIFF INIT: " << diff) - - - while (!seqLcs.collection()->empty()) { - ParentSuperselectorChunker superselectorChunker(seqLcs); - Node chunksResult = chunks(groupSeq1, groupSeq2, superselectorChunker); - diff.collection()->push_back(chunksResult); - - Node lcsWrapper = Node::createCollection(); - lcsWrapper.collection()->push_back(seqLcs.collection()->front()); - seqLcs.collection()->pop_front(); - diff.collection()->push_back(lcsWrapper); - - if (groupSeq1.collection()->size()) groupSeq1.collection()->pop_front(); - if (groupSeq2.collection()->size()) groupSeq2.collection()->pop_front(); - } - - DEBUG_PRINTLN(SUBWEAVE, "DIFF POST LCS: " << diff) - - - DEBUG_PRINTLN(SUBWEAVE, "CHUNKS: ONE=" << groupSeq1 << " TWO=" << groupSeq2) - - - SubweaveEmptyChunker emptyChunker; - Node chunksResult = chunks(groupSeq1, groupSeq2, emptyChunker); - diff.collection()->push_back(chunksResult); - - - DEBUG_PRINTLN(SUBWEAVE, "DIFF POST CHUNKS: " << diff) - - - diff.collection()->insert(diff.collection()->end(), fin.collection()->begin(), fin.collection()->end()); - - DEBUG_PRINTLN(SUBWEAVE, "DIFF POST FIN MAPPED: " << diff) - - // JMA - filter out the empty nodes (use a new collection, since iterator erase() invalidates the old collection) - Node diffFiltered = Node::createCollection(); - for (NodeDeque::iterator diffIter = diff.collection()->begin(), diffEndIter = diff.collection()->end(); - diffIter != diffEndIter; ++diffIter) { - Node& node = *diffIter; - if (node.collection() && !node.collection()->empty()) { - diffFiltered.collection()->push_back(node); - } - } - diff = diffFiltered; - - DEBUG_PRINTLN(SUBWEAVE, "DIFF POST REJECT: " << diff) - - - Node pathsResult = paths(diff); - - DEBUG_PRINTLN(SUBWEAVE, "PATHS: " << pathsResult) - - - // We're flattening in place - for (NodeDeque::iterator pathsIter = pathsResult.collection()->begin(), pathsEndIter = pathsResult.collection()->end(); - pathsIter != pathsEndIter; ++pathsIter) { - - Node& child = *pathsIter; - child = flatten(child); - } - - DEBUG_PRINTLN(SUBWEAVE, "FLATTENED: " << pathsResult) - - - /* - TODO: implement - rejected = mapped.reject {|p| path_has_two_subjects?(p)} - $stderr.puts "REJECTED: #{rejected}" - */ - - - return pathsResult; - - } - /* - // disabled to avoid clang warning [-Wunused-function] - static Node subweaveNaive(const Node& one, const Node& two) { - Node out = Node::createCollection(); - - // Check for the simple cases - if (one.isNil()) { - out.collection()->push_back(two.klone()); - } else if (two.isNil()) { - out.collection()->push_back(one.klone()); - } else { - // Do the naive implementation. pOne = A B and pTwo = C D ...yields... A B C D and C D A B - // See https://gist.github.com/nex3/7609394 for details. - - Node firstPerm = one.klone(); - Node twoCloned = two.klone(); - firstPerm.plus(twoCloned); - out.collection()->push_back(firstPerm); - - Node secondPerm = two.klone(); - Node oneCloned = one.klone(); - secondPerm.plus(oneCloned ); - out.collection()->push_back(secondPerm); - } - - return out; - } - */ - - - /* - This is the equivalent of ruby's Sequence.weave. - - The following is the modified version of the ruby code that was more portable to C++. You - should be able to drop it into ruby 3.2.19 and get the same results from ruby sass. - - def weave(path) - # This function works by moving through the selector path left-to-right, - # building all possible prefixes simultaneously. These prefixes are - # `befores`, while the remaining parenthesized suffixes is `afters`. - befores = [[]] - afters = path.dup - - until afters.empty? - current = afters.shift.dup - last_current = [current.pop] - - tempResult = [] - - for before in befores do - sub = subweave(before, current) - if sub.nil? - next - end - - for seqs in sub do - tempResult.push(seqs + last_current) - end - end - - befores = tempResult - - end - - return befores - end - */ - /* - def weave(path) - befores = [[]] - afters = path.dup - - until afters.empty? - current = afters.shift.dup - - last_current = [current.pop] - - - tempResult = [] - - for before in befores do - sub = subweave(before, current) - - if sub.nil? - next [] - end - - - for seqs in sub do - toPush = seqs + last_current - - tempResult.push(seqs + last_current) - end - - end - - befores = tempResult - - end - - return befores - end - */ - Node Extend::weave(Node& path) { - - DEBUG_PRINTLN(WEAVE, "WEAVE: " << path) - - Node befores = Node::createCollection(); - befores.collection()->push_back(Node::createCollection()); - - Node afters = Node::createCollection(); - afters.plus(path); - - while (!afters.collection()->empty()) { - Node current = afters.collection()->front().klone(); - afters.collection()->pop_front(); - DEBUG_PRINTLN(WEAVE, "CURRENT: " << current) - if (current.collection()->size() == 0) continue; - - Node last_current = Node::createCollection(); - last_current.collection()->push_back(current.collection()->back()); - current.collection()->pop_back(); - DEBUG_PRINTLN(WEAVE, "CURRENT POST POP: " << current) - DEBUG_PRINTLN(WEAVE, "LAST CURRENT: " << last_current) - - Node tempResult = Node::createCollection(); - - for (NodeDeque::iterator beforesIter = befores.collection()->begin(), beforesEndIter = befores.collection()->end(); beforesIter != beforesEndIter; beforesIter++) { - Node& before = *beforesIter; - - Node sub = subweave(before, current); - - DEBUG_PRINTLN(WEAVE, "SUB: " << sub) - - if (sub.isNil()) { - return Node::createCollection(); - } - - for (NodeDeque::iterator subIter = sub.collection()->begin(), subEndIter = sub.collection()->end(); subIter != subEndIter; subIter++) { - Node& seqs = *subIter; - - Node toPush = Node::createCollection(); - toPush.plus(seqs); - toPush.plus(last_current); - - // move line feed from inner to outer selector (very hacky indeed) - if (last_current.collection() && last_current.collection()->front().selector()) { - toPush.got_line_feed = last_current.collection()->front().got_line_feed; - last_current.collection()->front().selector()->has_line_feed(false); - last_current.collection()->front().got_line_feed = false; - } - - tempResult.collection()->push_back(toPush); - - } - } - - befores = tempResult; - - } - - return befores; - } - - - - /* - This is the equivalent of ruby's SimpleSequence.do_extend. - - // TODO: I think I have some modified ruby code to put here. Check. - */ - /* - ISSUES: - - Previous TODO: Do we need to group the results by extender? - - What does subject do in?: next unless unified = seq.members.last.unify(self_without_sel, subject?) - - IMPROVEMENT: The search for uniqueness at the end is not ideal since it's has to loop over everything... - - IMPROVEMENT: Check if the final search for uniqueness is doing anything that extendComplexSelector isn't already doing... - */ - template - class GroupByToAFunctor { - public: - KeyType operator()(SubSetMapPair& extPair) const { - Complex_Selector_Obj pSelector = extPair.first; - return pSelector; - } - }; - Node Extend::extendCompoundSelector(Compound_Selector_Ptr pSelector, CompoundSelectorSet& seen, bool isReplace) { - - /* this turned out to be too much overhead - probably due to holding a "Node" object - // check if we already extended this selector - // we can do this since subset_map is "static" - auto memoized = memoizeCompound.find(pSelector); - if (memoized != memoizeCompound.end()) { - return memoized->second.klone(); - } - */ - - DEBUG_EXEC(EXTEND_COMPOUND, printCompoundSelector(pSelector, "EXTEND COMPOUND: ")) - // TODO: Ruby has another loop here to skip certain members? - - // let RESULTS be an empty list of complex selectors - Node results = Node::createCollection(); - // extendedSelectors.got_line_feed = true; - - SubSetMapPairs entries = subset_map.get_v(pSelector); - - GroupByToAFunctor extPairKeyFunctor; - SubSetMapResults arr; - group_by_to_a(entries, extPairKeyFunctor, arr); - - SubSetMapLookups holder; - - // for each (EXTENDER, TARGET) in MAP.get(COMPOUND): - for (SubSetMapResult& groupedPair : arr) { - - Complex_Selector_Obj seq = groupedPair.first; - SubSetMapPairs& group = groupedPair.second; - - DEBUG_EXEC(EXTEND_COMPOUND, printComplexSelector(seq, "SEQ: ")) - - Compound_Selector_Obj pSels = SASS_MEMORY_NEW(Compound_Selector, pSelector->pstate()); - for (SubSetMapPair& pair : group) { - pair.second->extended(true); - pSels->concat(pair.second); - } - - DEBUG_EXEC(EXTEND_COMPOUND, printCompoundSelector(pSels, "SELS: ")) - - // The selector up to where the @extend is (ie, the thing to merge) - Complex_Selector_Ptr pExtComplexSelector = seq; - - // TODO: This can return a Compound_Selector with no elements. Should that just be returning NULL? - // RUBY: self_without_sel = Sass::Util.array_minus(members, sels) - Compound_Selector_Obj pSelectorWithoutExtendSelectors = pSelector->minus(pSels); - - DEBUG_EXEC(EXTEND_COMPOUND, printCompoundSelector(pSelector, "MEMBERS: ")) - DEBUG_EXEC(EXTEND_COMPOUND, printCompoundSelector(pSelectorWithoutExtendSelectors, "SELF_WO_SEL: ")) - - Compound_Selector_Obj pInnermostCompoundSelector = pExtComplexSelector->last()->head(); - - if (!pInnermostCompoundSelector) { - pInnermostCompoundSelector = SASS_MEMORY_NEW(Compound_Selector, pSelector->pstate()); - } - Compound_Selector_Obj pUnifiedSelector = pInnermostCompoundSelector->unify_with(pSelectorWithoutExtendSelectors); - - DEBUG_EXEC(EXTEND_COMPOUND, printCompoundSelector(pInnermostCompoundSelector, "LHS: ")) - DEBUG_EXEC(EXTEND_COMPOUND, printCompoundSelector(pSelectorWithoutExtendSelectors, "RHS: ")) - DEBUG_EXEC(EXTEND_COMPOUND, printCompoundSelector(pUnifiedSelector, "UNIFIED: ")) - - // RUBY: next unless unified - if (!pUnifiedSelector || pUnifiedSelector->length() == 0) { - continue; - } - - // TODO: implement the parent directive match (if necessary based on test failures) - // next if group.map {|e, _| check_directives_match!(e, parent_directives)}.none? - - // TODO: This seems a little fishy to me. See if it causes any problems. From the ruby, we should be able to just - // get rid of the last Compound_Selector and replace it with this one. I think the reason this code is more - // complex is that Complex_Selector contains a combinator, but in ruby combinators have already been filtered - // out and aren't operated on. - Complex_Selector_Obj pNewSelector = SASS_MEMORY_CLONE(pExtComplexSelector); // ->first(); - - Complex_Selector_Obj pNewInnerMost = SASS_MEMORY_NEW(Complex_Selector, pSelector->pstate(), Complex_Selector::ANCESTOR_OF, pUnifiedSelector, NULL); - - Complex_Selector::Combinator combinator = pNewSelector->clear_innermost(); - pNewSelector->set_innermost(pNewInnerMost, combinator); - -#ifdef DEBUG - ComplexSelectorSet debugSet; - debugSet = pNewSelector->sources(); - if (debugSet.size() > 0) { - throw std::runtime_error("The new selector should start with no sources. Something needs to be cloned to fix this."); - } - debugSet = pExtComplexSelector->sources(); - if (debugSet.size() > 0) { - throw std::runtime_error("The extension selector from our subset map should not have sources. These will bleed to the new selector. Something needs to be cloned to fix this."); - } -#endif - - - // if (pSelector && pSelector->has_line_feed()) pNewInnerMost->has_line_feed(true); - // Set the sources on our new Complex_Selector to the sources of this simple sequence plus the thing we're extending. - DEBUG_PRINTLN(EXTEND_COMPOUND, "SOURCES SETTING ON NEW SEQ: " << complexSelectorToNode(pNewSelector)) - - DEBUG_EXEC(EXTEND_COMPOUND, ComplexSelectorSet oldSet = pNewSelector->sources(); printSourcesSet(oldSet, "SOURCES NEW SEQ BEGIN: ")) - - // I actually want to create a copy here (performance!) - ComplexSelectorSet newSourcesSet = pSelector->sources(); // XXX - DEBUG_EXEC(EXTEND_COMPOUND, printSourcesSet(newSourcesSet, "SOURCES THIS EXTEND: ")) - - newSourcesSet.insert(pExtComplexSelector); - DEBUG_EXEC(EXTEND_COMPOUND, printSourcesSet(newSourcesSet, "SOURCES WITH NEW SOURCE: ")) - - // RUBY: new_seq.add_sources!(sources + [seq]) - pNewSelector->addSources(newSourcesSet); - - DEBUG_EXEC(EXTEND_COMPOUND, ComplexSelectorSet newSet = pNewSelector->sources(); printSourcesSet(newSet, "SOURCES ON NEW SELECTOR AFTER ADD: ")) - DEBUG_EXEC(EXTEND_COMPOUND, printSourcesSet(pSelector->sources(), "SOURCES THIS EXTEND WHICH SHOULD BE SAME STILL: ")) - - - if (pSels->has_line_feed()) pNewSelector->has_line_feed(true); - - holder.push_back(std::make_pair(pSels, pNewSelector)); - } - - - for (SubSetMapLookup& pair : holder) { - - Compound_Selector_Obj pSels = pair.first; - Complex_Selector_Obj pNewSelector = pair.second; - - - // RUBY??: next [] if seen.include?(sels) - if (seen.find(pSels) != seen.end()) { - continue; - } - - - CompoundSelectorSet recurseSeen(seen); - recurseSeen.insert(pSels); - - - DEBUG_PRINTLN(EXTEND_COMPOUND, "RECURSING DO EXTEND: " << complexSelectorToNode(pNewSelector)) - Node recurseExtendedSelectors = extendComplexSelector(pNewSelector, recurseSeen, isReplace, false); // !:isOriginal - - DEBUG_PRINTLN(EXTEND_COMPOUND, "RECURSING DO EXTEND RETURN: " << recurseExtendedSelectors) - - for (NodeDeque::iterator iterator = recurseExtendedSelectors.collection()->begin(), endIterator = recurseExtendedSelectors.collection()->end(); - iterator != endIterator; ++iterator) { - Node newSelector = *iterator; - -// DEBUG_PRINTLN(EXTEND_COMPOUND, "EXTENDED AT THIS POINT: " << results) -// DEBUG_PRINTLN(EXTEND_COMPOUND, "SELECTOR EXISTS ALREADY: " << newSelector << " " << results.contains(newSelector, false /*simpleSelectorOrderDependent*/)); - - if (!results.contains(newSelector)) { -// DEBUG_PRINTLN(EXTEND_COMPOUND, "ADDING NEW SELECTOR") - results.collection()->push_back(newSelector); - } - } - } - - DEBUG_EXEC(EXTEND_COMPOUND, printCompoundSelector(pSelector, "EXTEND COMPOUND END: ")) - - // this turned out to be too much overhead - // memory results in a map table - since extending is very expensive - // memoizeCompound.insert(std::pair(pSelector, results)); - - return results; - } - - - // check if selector has something to be extended by subset_map - bool Extend::complexSelectorHasExtension(Complex_Selector_Ptr selector, CompoundSelectorSet& seen) { - - bool hasExtension = false; - - Complex_Selector_Obj pIter = selector; - - while (!hasExtension && pIter) { - Compound_Selector_Obj pHead = pIter->head(); - - if (pHead) { - SubSetMapPairs entries = subset_map.get_v(pHead); - for (SubSetMapPair ext : entries) { - // check if both selectors have the same media block parent - // if (ext.first->media_block() == pComplexSelector->media_block()) continue; - if (ext.second->media_block() == 0) continue; - if (pHead->media_block() && - ext.second->media_block()->media_queries() && - pHead->media_block()->media_queries() - ) { - std::string query_left(ext.second->media_block()->media_queries()->to_string()); - std::string query_right(pHead->media_block()->media_queries()->to_string()); - if (query_left == query_right) continue; - } - - // fail if one goes across media block boundaries - std::stringstream err; - std::string cwd(Sass::File::get_cwd()); - ParserState pstate(ext.second->pstate()); - std::string rel_path(Sass::File::abs2rel(pstate.path, cwd, cwd)); - err << "You may not @extend an outer selector from within @media.\n"; - err << "You may only @extend selectors within the same directive.\n"; - err << "From \"@extend " << ext.second->to_string() << "\""; - err << " on line " << pstate.line+1 << " of " << rel_path << "\n"; - error(err.str(), selector->pstate(), eval->exp.traces); - } - if (entries.size() > 0) hasExtension = true; - } - - pIter = pIter->tail(); - } - - return hasExtension; - } - - - /* - This is the equivalent of ruby's Sequence.do_extend. - - // TODO: I think I have some modified ruby code to put here. Check. - */ - /* - ISSUES: - - check to automatically include combinators doesn't transfer over to libsass' data model where - the combinator and compound selector are one unit - next [[sseq_or_op]] unless sseq_or_op.is_a?(SimpleSequence) - */ - Node Extend::extendComplexSelector(Complex_Selector_Ptr selector, CompoundSelectorSet& seen, bool isReplace, bool isOriginal) { - - // check if we already extended this selector - // we can do this since subset_map is "static" - auto memoized = memoizeComplex.find(selector); - if (memoized != memoizeComplex.end()) { - return memoized->second; - } - - // convert the input selector to extend node format - Node complexSelector = complexSelectorToNode(selector); - DEBUG_PRINTLN(EXTEND_COMPLEX, "EXTEND COMPLEX: " << complexSelector) - - // let CHOICES be an empty list of selector-lists - // create new collection to hold the results - Node choices = Node::createCollection(); - - // for each compound selector COMPOUND in COMPLEX: - for (Node& sseqOrOp : *complexSelector.collection()) { - - DEBUG_PRINTLN(EXTEND_COMPLEX, "LOOP: " << sseqOrOp) - - // If it's not a selector (meaning it's a combinator), just include it automatically - // RUBY: next [[sseq_or_op]] unless sseq_or_op.is_a?(SimpleSequence) - if (!sseqOrOp.isSelector()) { - // Wrap our Combinator in two collections to match ruby. This is essentially making a collection Node - // with one collection child. The collection child represents a Complex_Selector that is only a combinator. - Node outer = Node::createCollection(); - Node inner = Node::createCollection(); - outer.collection()->push_back(inner); - inner.collection()->push_back(sseqOrOp); - choices.collection()->push_back(outer); - continue; - } - - // verified now that node is a valid selector - Complex_Selector_Obj sseqSel = sseqOrOp.selector(); - Compound_Selector_Obj sseqHead = sseqSel->head(); - - // let EXTENDED be extend_compound(COMPOUND, SEEN) - // extend the compound selector against the given subset_map - // RUBY: extended = sseq_or_op.do_extend(extends, parent_directives, replace, seen) - Node extended = extendCompoundSelector(sseqHead, seen, isReplace); // slow(17%)! - if (sseqOrOp.got_line_feed) extended.got_line_feed = true; - DEBUG_PRINTLN(EXTEND_COMPLEX, "EXTENDED: " << extended) - - // Prepend the Compound_Selector based on the choices logic; choices seems to be extend but with a ruby - // Array instead of a Sequence due to the member mapping: choices = extended.map {|seq| seq.members} - // RUBY: extended.first.add_sources!([self]) if original && !has_placeholder? - if (isOriginal && !selector->has_placeholder()) { - ComplexSelectorSet srcset; - srcset.insert(selector); - sseqSel->addSources(srcset); - // DEBUG_PRINTLN(EXTEND_COMPLEX, "ADD SOURCES: " << *pComplexSelector) - } - - bool isSuperselector = false; - // if no complex selector in EXTENDED is a superselector of COMPOUND: - for (Node& childNode : *extended.collection()) { - Complex_Selector_Obj pExtensionSelector = nodeToComplexSelector(childNode); - if (pExtensionSelector->is_superselector_of(sseqSel)) { - isSuperselector = true; - break; - } - } - - if (!isSuperselector) { - // add a complex selector composed only of COMPOUND to EXTENDED - if (sseqOrOp.got_line_feed) sseqSel->has_line_feed(sseqOrOp.got_line_feed); - extended.collection()->push_front(complexSelectorToNode(sseqSel)); - } - - DEBUG_PRINTLN(EXTEND_COMPLEX, "CHOICES UNSHIFTED: " << extended) - - // add EXTENDED to CHOICES - // Aggregate our current extensions - choices.collection()->push_back(extended); - } - - - DEBUG_PRINTLN(EXTEND_COMPLEX, "EXTENDED NOT EXPANDED: " << choices) - - - - // Ruby Equivalent: paths - Node paths = Sass::paths(choices); - - DEBUG_PRINTLN(EXTEND_COMPLEX, "PATHS: " << paths) - - // let WEAVES be an empty list of selector lists - Node weaves = Node::createCollection(); - - // for each list of complex selectors PATH in paths(CHOICES): - for (Node& path : *paths.collection()) { - // add weave(PATH) to WEAVES - Node weaved = weave(path); // slow(12%)! - weaved.got_line_feed = path.got_line_feed; - weaves.collection()->push_back(weaved); - } - - DEBUG_PRINTLN(EXTEND_COMPLEX, "WEAVES: " << weaves) - - // Ruby Equivalent: trim - Node trimmed(trim(weaves, isReplace)); // slow(19%)! - - DEBUG_PRINTLN(EXTEND_COMPLEX, "TRIMMED: " << trimmed) - - // Ruby Equivalent: flatten - Node flattened(flatten(trimmed, 1)); - - DEBUG_PRINTLN(EXTEND_COMPLEX, ">>>>> EXTENDED: " << extendedSelectors) - DEBUG_PRINTLN(EXTEND_COMPLEX, "EXTEND COMPLEX END: " << complexSelector) - - // memory results in a map table - since extending is very expensive - memoizeComplex.insert(std::pair(selector, flattened)); - - // return trim(WEAVES) - return flattened; - } - - - - /* - This is the equivalent of ruby's CommaSequence.do_extend. - */ - // We get a selector list with has something to extend and a subset_map with - // all extenders. Pick the ones that match our selectors in the list. - Selector_List_Ptr Extend::extendSelectorList(Selector_List_Obj pSelectorList, bool isReplace, bool& extendedSomething, CompoundSelectorSet& seen) { - - Selector_List_Obj pNewSelectors = SASS_MEMORY_NEW(Selector_List, pSelectorList->pstate(), pSelectorList->length()); - - // check if we already extended this selector - // we can do this since subset_map is "static" - auto memoized = memoizeList.find(pSelectorList); - if (memoized != memoizeList.end()) { - extendedSomething = true; - return memoized->second; - } - - extendedSomething = false; - // process each comlplex selector in the selector list. - // Find the ones that can be extended by given subset_map. - for (size_t index = 0, length = pSelectorList->length(); index < length; index++) { - Complex_Selector_Obj pSelector = (*pSelectorList)[index]; - - // ruby sass seems to keep a list of things that have extensions and then only extend those. We don't currently do that. - // Since it's not that expensive to check if an extension exists in the subset map and since it can be relatively expensive to - // run through the extend code (which does a data model transformation), check if there is anything to extend before doing - // the extend. We might be able to optimize extendComplexSelector, but this approach keeps us closer to ruby sass (which helps - // when debugging). - if (!complexSelectorHasExtension(pSelector, seen)) { - pNewSelectors->append(pSelector); - continue; - } - - // complexSelectorHasExtension was true! - extendedSomething = true; - - // now do the actual extension of the complex selector - Node extendedSelectors = extendComplexSelector(pSelector, seen, isReplace, true); - - if (!pSelector->has_placeholder()) { - Node nSelector(complexSelectorToNode(pSelector)); - if (!extendedSelectors.contains(nSelector)) { - pNewSelectors->append(pSelector); - continue; - } - } - - bool doReplace = isReplace; - for (Node& childNode : *extendedSelectors.collection()) { - // When it is a replace, skip the first one, unless there is only one - if(doReplace && extendedSelectors.collection()->size() > 1 ) { - doReplace = false; - continue; - } - pNewSelectors->append(nodeToComplexSelector(childNode)); - } - } - - Remove_Placeholders remove_placeholders; - // it seems that we have to remove the place holders early here - // normally we do this as the very last step (compare to ruby sass) - pNewSelectors = remove_placeholders.remove_placeholders(pNewSelectors); - - // unwrap all wrapped selectors with inner lists - for (Complex_Selector_Obj cur : pNewSelectors->elements()) { - // process tails - while (cur) { - // process header - if (cur->head() && seen.find(cur->head()) == seen.end()) { - CompoundSelectorSet recseen(seen); - recseen.insert(cur->head()); - // create a copy since we add multiple items if stuff get unwrapped - Compound_Selector_Obj cpy_head = SASS_MEMORY_NEW(Compound_Selector, cur->pstate()); - for (Simple_Selector_Obj hs : *cur->head()) { - if (Wrapped_Selector_Obj ws = Cast(hs)) { - ws->selector(SASS_MEMORY_CLONE(ws->selector())); - if (Selector_List_Obj sl = Cast(ws->selector())) { - // special case for ruby ass - if (sl->empty()) { - // this seems inconsistent but it is how ruby sass seems to remove parentheses - cpy_head->append(SASS_MEMORY_NEW(Element_Selector, hs->pstate(), ws->name())); - } - // has wrapped not selectors - else if (ws->name() == ":not") { - // extend the inner list of wrapped selector - bool extended = false; - Selector_List_Obj ext_sl = extendSelectorList(sl, false, extended, recseen); - for (size_t i = 0; i < ext_sl->length(); i += 1) { - if (Complex_Selector_Obj ext_cs = ext_sl->at(i)) { - // create clones for wrapped selector and the inner list - Wrapped_Selector_Obj cpy_ws = SASS_MEMORY_COPY(ws); - Selector_List_Obj cpy_ws_sl = SASS_MEMORY_NEW(Selector_List, sl->pstate()); - // remove parent selectors from inner selector - Compound_Selector_Obj ext_head = NULL; - if (ext_cs->first()) ext_head = ext_cs->first()->head(); - if (ext_head && ext_head && ext_head->length() > 0) { - cpy_ws_sl->append(ext_cs->first()); - } - // assign list to clone - cpy_ws->selector(cpy_ws_sl); - // append the clone - cpy_head->append(cpy_ws); - } - } - if (eval && extended) { - eval->exp.selector_stack.push_back(pNewSelectors); - cpy_head->perform(eval); - eval->exp.selector_stack.pop_back(); - } - } - // has wrapped selectors - else { - Wrapped_Selector_Obj cpy_ws = SASS_MEMORY_COPY(ws); - Selector_List_Obj ext_sl = extendSelectorList(sl, recseen); - cpy_ws->selector(ext_sl); - cpy_head->append(cpy_ws); - } - } else { - cpy_head->append(hs); - } - } else { - cpy_head->append(hs); - } - } - // replace header - cur->head(cpy_head); - } - // process tail - cur = cur->tail(); - } - } - - // memory results in a map table - since extending is very expensive - memoizeList.insert(std::pair(pSelectorList, pNewSelectors)); - - return pNewSelectors.detach(); - - } - - - bool shouldExtendBlock(Block_Obj b) { - - // If a block is empty, there's no reason to extend it since any rules placed on this block - // won't have any output. The main benefit of this is for structures like: - // - // .a { - // .b { - // x: y; - // } - // } - // - // We end up visiting two rulesets (one with the selector .a and the other with the selector .a .b). - // In this case, we don't want to try to pull rules onto .a since they won't get output anyway since - // there are no child statements. However .a .b should have extensions applied. - - for (size_t i = 0, L = b->length(); i < L; ++i) { - Statement_Obj stm = b->at(i); - - if (Cast(stm)) { - // Do nothing. This doesn't count as a statement that causes extension since we'll - // iterate over this rule set in a future visit and try to extend it. - } - else { - return true; - } - } - - return false; - - } - - - // Extend a ruleset by extending the selectors and updating them on the ruleset. The block's rules don't need to change. - // Every Ruleset in the whole tree is calling this function. We decide if there - // was is @extend that matches our selector. If we find one, we will go further - // and call the extend magic for our selector. The subset_map contains all blocks - // where @extend was found. Pick the ones that match our selector! - void Extend::extendObjectWithSelectorAndBlock(Ruleset_Ptr pObject) { - - DEBUG_PRINTLN(EXTEND_OBJECT, "FOUND SELECTOR: " << Cast(pObject->selector())->to_string()) - - // Ruby sass seems to filter nodes that don't have any content well before we get here. - // I'm not sure the repercussions of doing so, so for now, let's just not extend things - // that won't be output later. Profiling shows this may us 0.2% or so. - if (!shouldExtendBlock(pObject->block())) { - DEBUG_PRINTLN(EXTEND_OBJECT, "RETURNING WITHOUT EXTEND ATTEMPT") - return; - } - - bool extendedSomething = false; - - CompoundSelectorSet seen; - Selector_List_Obj pNewSelectorList = extendSelectorList(pObject->selector(), false, extendedSomething, seen); - - if (extendedSomething && pNewSelectorList) { - DEBUG_PRINTLN(EXTEND_OBJECT, "EXTEND ORIGINAL SELECTORS: " << pObject->selector()->to_string()) - DEBUG_PRINTLN(EXTEND_OBJECT, "EXTEND SETTING NEW SELECTORS: " << pNewSelectorList->to_string()) - pNewSelectorList->remove_parent_selectors(); - pObject->selector(pNewSelectorList); - } else { - DEBUG_PRINTLN(EXTEND_OBJECT, "EXTEND DID NOT TRY TO EXTEND ANYTHING") - } - } - - Extend::Extend(Subset_Map& ssm) - : subset_map(ssm), eval(NULL) - { } - - void Extend::setEval(Eval& e) { - eval = &e; - } - - void Extend::operator()(Block_Ptr b) - { - for (size_t i = 0, L = b->length(); i < L; ++i) { - Statement_Obj stm = b->at(i); - stm->perform(this); - } - // do final check if everything was extended - // we set `extended` flag on extended selectors - if (b->is_root()) { - // debug_subset_map(subset_map); - for(auto const &it : subset_map.values()) { - Complex_Selector_Ptr sel = NULL; - Compound_Selector_Ptr ext = NULL; - if (it.first) sel = it.first->first(); - if (it.second) ext = it.second; - if (ext && (ext->extended() || ext->is_optional())) continue; - std::string str_sel(sel ? sel->to_string({ NESTED, 5 }) : "NULL"); - std::string str_ext(ext ? ext->to_string({ NESTED, 5 }) : "NULL"); - // debug_ast(sel, "sel: "); - // debug_ast(ext, "ext: "); - error("\"" + str_sel + "\" failed to @extend \"" + str_ext + "\".\n" - "The selector \"" + str_ext + "\" was not found.\n" - "Use \"@extend " + str_ext + " !optional\" if the" - " extend should be able to fail.", (ext ? ext->pstate() : NULL), eval->exp.traces); - } - } - - } - - void Extend::operator()(Ruleset_Ptr pRuleset) - { - extendObjectWithSelectorAndBlock( pRuleset ); - pRuleset->block()->perform(this); - } - - void Extend::operator()(Supports_Block_Ptr pFeatureBlock) - { - pFeatureBlock->block()->perform(this); - } - - void Extend::operator()(Media_Block_Ptr pMediaBlock) - { - pMediaBlock->block()->perform(this); - } - - void Extend::operator()(Directive_Ptr a) - { - // Selector_List_Ptr ls = Cast(a->selector()); - // selector_stack.push_back(ls); - if (a->block()) a->block()->perform(this); - // exp.selector_stack.pop_back(); - } -} diff --git a/src/libsass/src/extend.hpp b/src/libsass/src/extend.hpp deleted file mode 100644 index 03042f3e2..000000000 --- a/src/libsass/src/extend.hpp +++ /dev/null @@ -1,86 +0,0 @@ -#ifndef SASS_EXTEND_H -#define SASS_EXTEND_H - -#include -#include - -#include "ast.hpp" -#include "node.hpp" -#include "eval.hpp" -#include "operation.hpp" -#include "subset_map.hpp" -#include "ast_fwd_decl.hpp" - -namespace Sass { - - Node subweave(Node& one, Node& two); - - class Extend : public Operation_CRTP { - - Subset_Map& subset_map; - Eval* eval; - - void fallback_impl(AST_Node_Ptr n) { } - - private: - - std::unordered_map< - Selector_List_Obj, // key - Selector_List_Obj, // value - HashNodes, // hasher - CompareNodes // compare - > memoizeList; - - std::unordered_map< - Complex_Selector_Obj, // key - Node, // value - HashNodes, // hasher - CompareNodes // compare - > memoizeComplex; - - /* this turned out to be too much overhead - re-evaluate once we store an ast selector - std::unordered_map< - Compound_Selector_Obj, // key - Node, // value - HashNodes, // hasher - CompareNodes // compare - > memoizeCompound; - */ - - void extendObjectWithSelectorAndBlock(Ruleset_Ptr pObject); - Node extendComplexSelector(Complex_Selector_Ptr sel, CompoundSelectorSet& seen, bool isReplace, bool isOriginal); - Node extendCompoundSelector(Compound_Selector_Ptr sel, CompoundSelectorSet& seen, bool isReplace); - bool complexSelectorHasExtension(Complex_Selector_Ptr selector, CompoundSelectorSet& seen); - Node trim(Node& seqses, bool isReplace); - Node weave(Node& path); - - public: - void setEval(Eval& eval); - Selector_List_Ptr extendSelectorList(Selector_List_Obj pSelectorList, bool isReplace, bool& extendedSomething, CompoundSelectorSet& seen); - Selector_List_Ptr extendSelectorList(Selector_List_Obj pSelectorList, bool isReplace = false) { - bool extendedSomething = false; - CompoundSelectorSet seen; - return extendSelectorList(pSelectorList, isReplace, extendedSomething, seen); - } - Selector_List_Ptr extendSelectorList(Selector_List_Obj pSelectorList, CompoundSelectorSet& seen) { - bool isReplace = false; - bool extendedSomething = false; - return extendSelectorList(pSelectorList, isReplace, extendedSomething, seen); - } - Extend(Subset_Map&); - ~Extend() { } - - void operator()(Block_Ptr); - void operator()(Ruleset_Ptr); - void operator()(Supports_Block_Ptr); - void operator()(Media_Block_Ptr); - void operator()(Directive_Ptr); - - template - void fallback(U x) { return fallback_impl(x); } - }; - -} - -#endif diff --git a/src/libsass/src/extender.cpp b/src/libsass/src/extender.cpp new file mode 100644 index 000000000..b0add3857 --- /dev/null +++ b/src/libsass/src/extender.cpp @@ -0,0 +1,1186 @@ +// sass.hpp must go before all system headers to get the +// __EXTENSIONS__ fix on Solaris. +#include "sass.hpp" +#include "ast.hpp" + +#include "extender.hpp" +#include "permutate.hpp" +#include "dart_helpers.hpp" + +namespace Sass { + + // ########################################################################## + // Constructor without default [mode]. + // [traces] are needed to throw errors. + // ########################################################################## + Extender::Extender(Backtraces& traces) : + mode(NORMAL), + traces(traces), + selectors(), + extensions(), + extensionsByExtender(), + mediaContexts(), + sourceSpecificity(), + originals() + {} + + // ########################################################################## + // Constructor with specific [mode]. + // [traces] are needed to throw errors. + // ########################################################################## + Extender::Extender(ExtendMode mode, Backtraces& traces) : + mode(mode), + traces(traces), + selectors(), + extensions(), + extensionsByExtender(), + mediaContexts(), + sourceSpecificity(), + originals() + {} + + // ########################################################################## + // Extends [selector] with [source] extender and [targets] extendees. + // This works as though `source {@extend target}` were written in the + // stylesheet, with the exception that [target] can contain compound + // selectors which must be extended as a unit. + // ########################################################################## + SelectorListObj Extender::extend( + SelectorListObj& selector, + const SelectorListObj& source, + const SelectorListObj& targets, + Backtraces& traces) + { + return extendOrReplace(selector, source, targets, ExtendMode::TARGETS, traces); + } + // EO Extender::extend + + // ########################################################################## + // Returns a copy of [selector] with [targets] replaced by [source]. + // ########################################################################## + SelectorListObj Extender::replace( + SelectorListObj& selector, + const SelectorListObj& source, + const SelectorListObj& targets, + Backtraces& traces) + { + return extendOrReplace(selector, source, targets, ExtendMode::REPLACE, traces); + } + // EO Extender::replace + + // ########################################################################## + // A helper function for [extend] and [replace]. + // ########################################################################## + SelectorListObj Extender::extendOrReplace( + SelectorListObj& selector, + const SelectorListObj& source, + const SelectorListObj& targets, + const ExtendMode mode, + Backtraces& traces) + { + ExtSelExtMapEntry extenders; + + for (auto complex : source->elements()) { + // Extension.oneOff(complex as ComplexSelector) + extenders.insert(complex, Extension(complex)); + } + + for (auto complex : targets->elements()) { + + // This seems superfluous, check is done before!? + // if (complex->length() != 1) { + // error("complex selectors may not be extended.", complex->pstate(), traces); + // } + + if (const CompoundSelector* compound = complex->first()->getCompound()) { + + ExtSelExtMap extensions; + + for (const SimpleSelectorObj& simple : compound->elements()) { + extensions.insert(std::make_pair(simple, extenders)); + } + + Extender extender(mode, traces); + + if (!selector->is_invisible()) { + for (auto sel : selector->elements()) { + extender.originals.insert(sel); + } + } + + selector = extender.extendList(selector, extensions, {}); + + } + + } + + return selector; + + } + // EO extendOrReplace + + // ########################################################################## + // The set of all simple selectors in style rules handled + // by this extender. This includes simple selectors that + // were added because of downstream extensions. + // ########################################################################## + ExtSmplSelSet Extender::getSimpleSelectors() const + { + ExtSmplSelSet set; + for (auto& entry : selectors) { + set.insert(entry.first); + } + return set; + } + // EO getSimpleSelectors + + // ########################################################################## + // Check for extends that have not been satisfied. + // Returns true if any non-optional extension did not + // extend any selector. Updates the passed reference + // to point to that Extension for further analysis. + // ########################################################################## + bool Extender::checkForUnsatisfiedExtends(Extension& unsatisfied) const + { + ExtSmplSelSet originals = getSimpleSelectors(); + for (auto target : extensions) { + SimpleSelector* key = target.first; + ExtSelExtMapEntry& val = target.second; + if (originals.find(key) == originals.end()) { + const Extension& extension = val.front().second; + if (extension.isOptional) continue; + unsatisfied = extension; + return true; + } + } + return false; + } + // EO checkUnsatisfiedExtends + + // ########################################################################## + // Adds [selector] to this extender, with [selectorSpan] as the span covering + // the selector and [ruleSpan] as the span covering the entire style rule. + // Extends [selector] using any registered extensions, then returns an empty + // [ModifiableCssStyleRule] with the resulting selector. If any more relevant + // extensions are added, the returned rule is automatically updated. + // The [mediaContext] is the media query context in which the selector was + // defined, or `null` if it was defined at the top level of the document. + // ########################################################################## + void Extender::addSelector( + const SelectorListObj& selector, + const CssMediaRuleObj& mediaContext) + { + + // Note: dart-sass makes a copy here AFAICT + // Note: probably why we have originalStack + // SelectorListObj original = selector; + + if (!selector->isInvisible()) { + for (auto complex : selector->elements()) { + originals.insert(complex); + } + } + + if (!extensions.empty()) { + + SelectorListObj res = extendList(selector, extensions, mediaContext); + + selector->elements(res->elements()); + + } + + if (!mediaContext.isNull()) { + mediaContexts.insert(selector, mediaContext); + } + + registerSelector(selector, selector); + + } + // EO addSelector + + // ########################################################################## + // Registers the [SimpleSelector]s in [list] + // to point to [rule] in [selectors]. + // ########################################################################## + void Extender::registerSelector( + const SelectorListObj& list, + const SelectorListObj& rule) + { + if (list.isNull() || list->empty()) return; + for (auto complex : list->elements()) { + for (auto component : complex->elements()) { + if (auto compound = component->getCompound()) { + for (SimpleSelector* simple : compound->elements()) { + selectors[simple].insert(rule); + if (auto pseudo = simple->getPseudoSelector()) { + if (pseudo->selector()) { + auto sel = pseudo->selector(); + registerSelector(sel, rule); + } + } + } + } + } + } + } + // EO registerSelector + + // ########################################################################## + // Returns an extension that combines [left] and [right]. Throws + // a [SassException] if [left] and [right] have incompatible + // media contexts. Throws an [ArgumentError] if [left] + // and [right] don't have the same extender and target. + // ########################################################################## + Extension Extender::mergeExtension( + const Extension& lhs, + const Extension& rhs) + { + // If one extension is optional and doesn't add a + // special media context, it doesn't need to be merged. + if (rhs.isOptional && rhs.mediaContext.isNull()) return lhs; + if (lhs.isOptional && lhs.mediaContext.isNull()) return rhs; + + Extension rv(lhs); + // ToDo: is this right? + rv.isOptional = true; + rv.isOriginal = false; + return rv; + } + // EO mergeExtension + + // ########################################################################## + // Helper function to copy extension between maps + // ########################################################################## + // Seems only relevant for sass 4.0 modules + // ########################################################################## + /* void mapCopyExts( + ExtSelExtMap& dest, + const ExtSelExtMap& source) + { + for (auto it : source) { + SimpleSelectorObj key = it.first; + ExtSelExtMapEntry& inner = it.second; + ExtSelExtMap::iterator dmap = dest.find(key); + if (dmap == dest.end()) { + dest.insert(std::make_pair(key, inner)); + } + else { + ExtSelExtMapEntry& imap = dmap->second; + // ToDo: optimize ordered_map API! + // ToDo: we iterate and fetch the value + for (ComplexSelectorObj& it2 : inner) { + imap.insert(it2, inner.get(it2)); + } + } + } + } */ + // EO mapCopyExts + + // ########################################################################## + // Adds an extension to this extender. The [extender] is the selector for the + // style rule in which the extension is defined, and [target] is the selector + // passed to `@extend`. The [extend] provides the extend span and indicates + // whether the extension is optional. The [mediaContext] defines the media query + // context in which the extension is defined. It can only extend selectors + // within the same context. A `null` context indicates no media queries. + // ########################################################################## + // ToDo: rename extender to parent, since it is not involved in extending stuff + // ToDo: check why dart sass passes the ExtendRule around (is this the main selector?) + // ########################################################################## + // Note: this function could need some logic cleanup + // ########################################################################## + void Extender::addExtension( + const SelectorListObj& extender, + const SimpleSelectorObj& target, + const CssMediaRuleObj& mediaQueryContext, + bool is_optional) + { + + auto rules = selectors.find(target); + bool hasRule = rules != selectors.end(); + + ExtSelExtMapEntry newExtensions; + + // ToDo: we check this here first and fetch the same? item again after the loop!? + bool hasExistingExtensions = extensionsByExtender.find(target) != extensionsByExtender.end(); + + ExtSelExtMapEntry& sources = extensions[target]; + + for (auto& complex : extender->elements()) { + + Extension state(complex); + // ToDo: fine-tune public API + state.target = target; + state.isOptional = is_optional; + state.mediaContext = mediaQueryContext; + + if (sources.hasKey(complex)) { + // If there's already an extend from [extender] to [target], + // we don't need to re-run the extension. We may need to + // mark the extension as mandatory, though. + // sources.insert(complex, mergeExtension(existingState->second, state); + // ToDo: implement behavior once use case is found!? + continue; + } + + sources.insert(complex, state); + + for (auto& component : complex->elements()) { + if (auto compound = component->getCompound()) { + for (auto& simple : compound->elements()) { + extensionsByExtender[simple].push_back(state); + if (sourceSpecificity.find(simple) == sourceSpecificity.end()) { + // Only source specificity for the original selector is relevant. + // Selectors generated by `@extend` don't get new specificity. + sourceSpecificity[simple] = complex->maxSpecificity(); + } + } + } + } + + if (hasRule || hasExistingExtensions) { + newExtensions.insert(complex, state); + } + + } + // EO foreach complex + + if (newExtensions.empty()) { + return; + } + + ExtSelExtMap newExtensionsByTarget; + newExtensionsByTarget.insert(std::make_pair(target, newExtensions)); + // ToDo: do we really need to fetch again (see top off fn) + auto existingExtensions = extensionsByExtender.find(target); + if (existingExtensions != extensionsByExtender.end()) { + if (hasExistingExtensions && !existingExtensions->second.empty()) { + // Seems only relevant for sass 4.0 modules + // auto additionalExtensions = + extendExistingExtensions(existingExtensions->second, newExtensionsByTarget); + // Seems only relevant for sass 4.0 modules + /* if (!additionalExtensions.empty()) { + mapCopyExts(newExtensionsByTarget, additionalExtensions); + } */ + } + } + + if (hasRule) { + extendExistingStyleRules(selectors[target], newExtensionsByTarget); + } + + } + // EO addExtension + + // ########################################################################## + // Extend [extensions] using [newExtensions]. + // ########################################################################## + // Note: dart-sass throws an error in here + // ########################################################################## + void Extender::extendExistingStyleRules( + const ExtListSelSet& rules, + const ExtSelExtMap& newExtensions) + { + // Is a modifyableCssStyleRUle in dart sass + for (const SelectorListObj& rule : rules) { + const SelectorListObj& oldValue = SASS_MEMORY_COPY(rule); + CssMediaRuleObj mediaContext; + if (mediaContexts.hasKey(rule)) mediaContext = mediaContexts.get(rule); + SelectorListObj ext = extendList(rule, newExtensions, mediaContext); + // If no extends actually happenedit (for example becaues unification + // failed), we don't need to re-register the selector. + if (ObjEqualityFn(oldValue, ext)) continue; + rule->elements(ext->elements()); + registerSelector(rule, rule); + + } + } + // EO extendExistingStyleRules + + // ########################################################################## + // Extend [extensions] using [newExtensions]. Note that this does duplicate + // some work done by [_extendExistingStyleRules], but it's necessary to + // expand each extension's extender separately without reference to the full + // selector list, so that relevant results don't get trimmed too early. + // + // Returns extensions that should be added to [newExtensions] before + // extending selectors in order to properly handle extension loops such as: + // + // .c {x: y; @extend .a} + // .x.y.a {@extend .b} + // .z.b {@extend .c} + // + // Returns `null` (Note: empty map) if there are no extensions to add. + // ########################################################################## + // Note: maybe refactor to return `bool` (and pass reference) + // Note: dart-sass throws an error in here + // ########################################################################## + ExtSelExtMap Extender::extendExistingExtensions( + // Taking in a reference here makes MSVC debug stuck!? + const std::vector& oldExtensions, + const ExtSelExtMap& newExtensions) + { + + ExtSelExtMap additionalExtensions; + + // During the loop `oldExtensions` vector might be changed. + // Callers normally pass this from `extensionsByExtender` and + // that points back to the `sources` vector from `extensions`. + for (size_t i = 0, iL = oldExtensions.size(); i < iL; i += 1) { + const Extension& extension = oldExtensions[i]; + ExtSelExtMapEntry& sources = extensions[extension.target]; + std::vector selectors(extendComplex( + extension.extender, + newExtensions, + extension.mediaContext + )); + + if (selectors.empty()) { + continue; + } + + // ToDo: "catch" error from extend + + bool first = false, containsExtension = + ObjEqualityFn(selectors.front(), extension.extender); + for (const ComplexSelectorObj& complex : selectors) { + // If the output contains the original complex + // selector, there's no need to recreate it. + if (containsExtension && first) { + first = false; + continue; + } + + const Extension withExtender = + extension.withExtender(complex); + if (sources.hasKey(complex)) { + sources.insert(complex, mergeExtension( + sources.get(complex), withExtender)); + } + else { + sources.insert(complex, withExtender); + /* + // Seems only relevant for sass 4.0 modules + for (auto& component : complex->elements()) { + if (auto compound = component->getCompound()) { + for (auto& simple : compound->elements()) { + extensionsByExtender[simple].push_back(withExtender); + } + } + } + if (newExtensions.find(extension.target) != newExtensions.end()) { + additionalExtensions[extension.target].insert(complex, withExtender); + } + */ + } + } + + // If [selectors] doesn't contain [extension.extender], + // for example if it was replaced due to :not() expansion, + // we must get rid of the old version. + /* + // Seems only relevant for sass 4.0 modules + if (!containsExtension) { + sources.erase(extension.extender); + } + */ + + } + + return additionalExtensions; + + } + // EO extendExistingExtensions + + // ########################################################################## + // Extends [list] using [extensions]. + // ########################################################################## + SelectorListObj Extender::extendList( + const SelectorListObj& list, + const ExtSelExtMap& extensions, + const CssMediaRuleObj& mediaQueryContext) + { + + // This could be written more simply using [List.map], but we want to + // avoid any allocations in the common case where no extends apply. + std::vector extended; + for (size_t i = 0; i < list->length(); i++) { + const ComplexSelectorObj& complex = list->get(i); + std::vector result = + extendComplex(complex, extensions, mediaQueryContext); + if (result.empty()) { + if (!extended.empty()) { + extended.push_back(complex); + } + } + else { + if (extended.empty()) { + for (size_t n = 0; n < i; n += 1) { + extended.push_back(list->get(n)); + } + } + for (auto sel : result) { + extended.push_back(sel); + } + } + } + + if (extended.empty()) { + return list; + } + + SelectorListObj rv = SASS_MEMORY_NEW(SelectorList, list->pstate()); + rv->concat(trim(extended, originals)); + return rv; + + } + // EO extendList + + // ########################################################################## + // Extends [complex] using [extensions], and + // returns the contents of a [SelectorList]. + // ########################################################################## + std::vector Extender::extendComplex( + // Taking in a reference here makes MSVC debug stuck!? + const ComplexSelectorObj& complex, + const ExtSelExtMap& extensions, + const CssMediaRuleObj& mediaQueryContext) + { + + // The complex selectors that each compound selector in [complex.components] + // can expand to. + // + // For example, given + // + // .a .b {...} + // .x .y {@extend .b} + // + // this will contain + // + // [ + // [.a], + // [.b, .x .y] + // ] + // + // This could be written more simply using [List.map], but we want to avoid + // any allocations in the common case where no extends apply. + + std::vector result; + std::vector> extendedNotExpanded; + bool isOriginal = originals.find(complex) != originals.end(); + for (size_t i = 0; i < complex->length(); i += 1) { + const SelectorComponentObj& component = complex->get(i); + if (CompoundSelector* compound = Cast(component)) { + std::vector extended = extendCompound( + compound, extensions, mediaQueryContext, isOriginal); + if (extended.empty()) { + if (!extendedNotExpanded.empty()) { + extendedNotExpanded.push_back({ + compound->wrapInComplex() + }); + } + } + else { + // Note: dart-sass checks for null!? + if (extendedNotExpanded.empty()) { + for (size_t n = 0; n < i; n++) { + extendedNotExpanded.push_back({ + complex->at(n)->wrapInComplex() + }); + } + } + extendedNotExpanded.push_back(extended); + } + } + else { + // Note: dart-sass checks for null!? + if (!extendedNotExpanded.empty()) { + extendedNotExpanded.push_back({ + component->wrapInComplex() + }); + } + } + } + + // Note: dart-sass checks for null!? + if (extendedNotExpanded.empty()) { + return {}; + } + + bool first = true; + + // ToDo: either change weave or paths to work with the same data? + std::vector> + paths = permutate(extendedNotExpanded); + + for (const std::vector& path : paths) { + // Unpack the inner complex selector to component list + std::vector> _paths; + for (const ComplexSelectorObj& sel : path) { + _paths.insert(_paths.end(), sel->elements()); + } + + std::vector> weaved = weave(_paths); + + for (std::vector& components : weaved) { + + ComplexSelectorObj cplx = SASS_MEMORY_NEW(ComplexSelector, "[phony]"); + cplx->hasPreLineFeed(complex->hasPreLineFeed()); + for (auto& pp : path) { + if (pp->hasPreLineFeed()) { + cplx->hasPreLineFeed(true); + } + } + cplx->elements(components); + + // Make sure that copies of [complex] retain their status + // as "original" selectors. This includes selectors that + // are modified because a :not() was extended into. + if (first && originals.find(complex) != originals.end()) { + originals.insert(cplx); + } + first = false; + + result.push_back(cplx); + + } + + } + + return result; + } + // EO extendComplex + + // ########################################################################## + // Returns a one-off [Extension] whose + // extender is composed solely of [simple]. + // ########################################################################## + Extension Extender::extensionForSimple( + const SimpleSelectorObj& simple) const + { + Extension extension(simple->wrapInComplex()); + extension.specificity = maxSourceSpecificity(simple); + extension.isOriginal = true; + return extension; + } + // Extender::extensionForSimple + + // ########################################################################## + // Returns a one-off [Extension] whose extender is composed + // solely of a compound selector containing [simples]. + // ########################################################################## + Extension Extender::extensionForCompound( + // Taking in a reference here makes MSVC debug stuck!? + const std::vector& simples) const + { + CompoundSelectorObj compound = SASS_MEMORY_NEW(CompoundSelector, ParserState("[ext]")); + compound->concat(simples); + Extension extension(compound->wrapInComplex()); + // extension.specificity = sourceSpecificity[simple]; + extension.isOriginal = true; + return extension; + } + // EO extensionForCompound + + // ########################################################################## + // Extends [compound] using [extensions], and returns the + // contents of a [SelectorList]. The [inOriginal] parameter + // indicates whether this is in an original complex selector, + // meaning that [compound] should not be trimmed out. + // ########################################################################## + std::vector Extender::extendCompound( + const CompoundSelectorObj& compound, + const ExtSelExtMap& extensions, + const CssMediaRuleObj& mediaQueryContext, + bool inOriginal) + { + + // If there's more than one target and they all need to + // match, we track which targets are actually extended. + ExtSmplSelSet targetsUsed2; + + ExtSmplSelSet* targetsUsed = nullptr; + + if (mode != ExtendMode::NORMAL && extensions.size() > 1) { + targetsUsed = &targetsUsed2; + } + + std::vector result; + // The complex selectors produced from each component of [compound]. + std::vector> options; + + for (size_t i = 0; i < compound->length(); i++) { + const SimpleSelectorObj& simple = compound->get(i); + auto extended = extendSimple(simple, extensions, mediaQueryContext, targetsUsed); + if (extended.empty()) { + if (!options.empty()) { + options.push_back({ extensionForSimple(simple) }); + } + } + else { + if (options.empty()) { + if (i != 0) { + std::vector in; + for (size_t n = 0; n < i; n += 1) { + in.push_back(compound->get(n)); + } + options.push_back({ extensionForCompound(in) }); + } + } + options.insert(options.end(), + extended.begin(), extended.end()); + } + } + + if (options.empty()) { + return {}; + } + + // If [_mode] isn't [ExtendMode.normal] and we didn't use all + // the targets in [extensions], extension fails for [compound]. + if (targetsUsed != nullptr) { + + if (targetsUsed->size() != extensions.size()) { + if (!targetsUsed->empty()) { + return {}; + } + } + } + + // Optimize for the simple case of a single simple + // selector that doesn't need any unification. + if (options.size() == 1) { + std::vector exts = options[0]; + for (size_t n = 0; n < exts.size(); n += 1) { + exts[n].assertCompatibleMediaContext(mediaQueryContext, traces); + result.push_back(exts[n].extender); + } + return result; + } + + // Find all paths through [options]. In this case, each path represents a + // different unification of the base selector. For example, if we have: + // + // .a.b {...} + // .w .x {@extend .a} + // .y .z {@extend .b} + // + // then [options] is `[[.a, .w .x], [.b, .y .z]]` and `paths(options)` is + // + // [ + // [.a, .b], + // [.a, .y .z], + // [.w .x, .b], + // [.w .x, .y .z] + // ] + // + // We then unify each path to get a list of complex selectors: + // + // [ + // [.a.b], + // [.y .a.z], + // [.w .x.b], + // [.w .y .x.z, .y .w .x.z] + // ] + + bool first = mode != ExtendMode::REPLACE; + std::vector unifiedPaths; + std::vector> prePaths = permutate(options); + + for (size_t i = 0; i < prePaths.size(); i += 1) { + std::vector> complexes; + const std::vector& path = prePaths[i]; + if (first) { + // The first path is always the original selector. We can't just + // return [compound] directly because pseudo selectors may be + // modified, but we don't have to do any unification. + first = false; + CompoundSelectorObj mergedSelector = + SASS_MEMORY_NEW(CompoundSelector, "[ext]"); + for (size_t n = 0; n < path.size(); n += 1) { + const ComplexSelectorObj& sel = path[n].extender; + if (CompoundSelectorObj compound = Cast(sel->last())) { + mergedSelector->concat(compound->elements()); + } + } + complexes.push_back({ mergedSelector }); + } + else { + std::vector originals; + std::vector> toUnify; + + for (auto& state : path) { + if (state.isOriginal) { + const ComplexSelectorObj& sel = state.extender; + if (const CompoundSelector* compound = Cast(sel->last())) { + originals.insert(originals.end(), compound->last()); + } + } + else { + toUnify.push_back(state.extender->elements()); + } + } + if (!originals.empty()) { + CompoundSelectorObj merged = + SASS_MEMORY_NEW(CompoundSelector, "[phony]"); + merged->concat(originals); + toUnify.insert(toUnify.begin(), { merged }); + } + complexes = unifyComplex(toUnify); + if (complexes.empty()) { + return {}; + } + + } + + bool lineBreak = false; + // var specificity = _sourceSpecificityFor(compound); + for (const Extension& state : path) { + state.assertCompatibleMediaContext(mediaQueryContext, traces); + lineBreak = lineBreak || state.extender->hasPreLineFeed(); + // specificity = math.max(specificity, state.specificity); + } + + for (std::vector& components : complexes) { + auto sel = SASS_MEMORY_NEW(ComplexSelector, "[ext]"); + sel->hasPreLineFeed(lineBreak); + sel->elements(components); + unifiedPaths.push_back(sel); + } + + } + + return unifiedPaths; + } + // EO extendCompound + + // ########################################################################## + // Extends [simple] without extending the + // contents of any selector pseudos it contains. + // ########################################################################## + std::vector Extender::extendWithoutPseudo( + const SimpleSelectorObj& simple, + const ExtSelExtMap& extensions, + ExtSmplSelSet* targetsUsed) const + { + + auto extension = extensions.find(simple); + if (extension == extensions.end()) return {}; + const ExtSelExtMapEntry& extenders = extension->second; + + if (targetsUsed != nullptr) { + targetsUsed->insert(simple); + } + if (mode == ExtendMode::REPLACE) { + return extenders.values(); + } + + const std::vector& + values = extenders.values(); + std::vector result; + result.reserve(values.size() + 1); + result.push_back(extensionForSimple(simple)); + result.insert(result.end(), values.begin(), values.end()); + return result; + } + // EO extendWithoutPseudo + + // ########################################################################## + // Extends [simple] and also extending the + // contents of any selector pseudos it contains. + // ########################################################################## + std::vector> Extender::extendSimple( + const SimpleSelectorObj& simple, + const ExtSelExtMap& extensions, + const CssMediaRuleObj& mediaQueryContext, + ExtSmplSelSet* targetsUsed) + { + if (Pseudo_Selector* pseudo = Cast(simple)) { + if (pseudo->selector()) { + std::vector> merged; + std::vector extended = + extendPseudo(pseudo, extensions, mediaQueryContext); + for (Pseudo_Selector_Obj& extend : extended) { + SimpleSelectorObj simple = extend; + std::vector result = + extendWithoutPseudo(simple, extensions, targetsUsed); + if (result.empty()) result = { extensionForSimple(extend) }; + merged.push_back(result); + } + if (!extended.empty()) { + return merged; + } + } + } + std::vector result = + extendWithoutPseudo(simple, extensions, targetsUsed); + if (result.empty()) return {}; + return { result }; + } + // extendSimple + + // ########################################################################## + // Inner loop helper for [extendPseudo] function + // ########################################################################## + std::vector Extender::extendPseudoComplex( + const ComplexSelectorObj& complex, + const Pseudo_Selector_Obj& pseudo, + const CssMediaRuleObj& mediaQueryContext) + { + + if (complex->length() != 1) { return { complex }; } + auto compound = Cast(complex->get(0)); + if (compound == nullptr) { return { complex }; } + if (compound->length() != 1) { return { complex }; } + auto innerPseudo = Cast(compound->get(0)); + if (innerPseudo == nullptr) { return { complex }; } + if (!innerPseudo->selector()) { return { complex }; } + + std::string name(pseudo->normalized()); + + if (name == "not") { + // In theory, if there's a `:not` nested within another `:not`, the + // inner `:not`'s contents should be unified with the return value. + // For example, if `:not(.foo)` extends `.bar`, `:not(.bar)` should + // become `.foo:not(.bar)`. However, this is a narrow edge case and + // supporting it properly would make this code and the code calling it + // a lot more complicated, so it's not supported for now. + if (innerPseudo->normalized() != "matches") return {}; + return innerPseudo->selector()->elements(); + } + else if (name == "matches" && name == "any" && name == "current" && name == "nth-child" && name == "nth-last-child") { + // As above, we could theoretically support :not within :matches, but + // doing so would require this method and its callers to handle much + // more complex cases that likely aren't worth the pain. + if (innerPseudo->name() != pseudo->name()) return {}; + if (!ObjEquality()(innerPseudo->argument(), pseudo->argument())) return {}; + return innerPseudo->selector()->elements(); + } + else if (name == "has" && name == "host" && name == "host-context" && name == "slotted") { + // We can't expand nested selectors here, because each layer adds an + // additional layer of semantics. For example, `:has(:has(img))` + // doesn't match `
` but `:has(img)` does. + return { complex }; + } + + return {}; + + } + // EO extendPseudoComplex + + // ########################################################################## + // Extends [pseudo] using [extensions], and returns + // a list of resulting pseudo selectors. + // ########################################################################## + std::vector Extender::extendPseudo( + const Pseudo_Selector_Obj& pseudo, + const ExtSelExtMap& extensions, + const CssMediaRuleObj& mediaQueryContext) + { + auto selector = pseudo->selector(); + SelectorListObj extended = extendList( + selector, extensions, mediaQueryContext); + if (!extended || !pseudo || !pseudo->selector()) { return {}; } + if (ObjEqualityFn(pseudo->selector(), extended)) { return {}; } + + // For `:not()`, we usually want to get rid of any complex selectors because + // that will cause the selector to fail to parse on all browsers at time of + // writing. We can keep them if either the original selector had a complex + // selector, or the result of extending has only complex selectors, because + // either way we aren't breaking anything that isn't already broken. + std::vector complexes = extended->elements(); + + if (pseudo->normalized() == "not") { + if (!hasAny(pseudo->selector()->elements(), hasMoreThanOne)) { + if (hasAny(extended->elements(), hasExactlyOne)) { + complexes.clear(); + for (auto& complex : extended->elements()) { + if (complex->length() <= 1) { + complexes.push_back(complex); + } + } + } + } + } + + std::vector expanded = expand( + complexes, extendPseudoComplex, pseudo, mediaQueryContext); + + // Older browsers support `:not`, but only with a single complex selector. + // In order to support those browsers, we break up the contents of a `:not` + // unless it originally contained a selector list. + if (pseudo->normalized() == "not") { + if (pseudo->selector()->length() == 1) { + std::vector pseudos; + for (size_t i = 0; i < expanded.size(); i += 1) { + pseudos.push_back(pseudo->withSelector( + expanded[i]->wrapInList() + )); + } + return pseudos; + } + } + + SelectorListObj list = SASS_MEMORY_NEW(SelectorList, "[phony]"); + list->concat(complexes); + return { pseudo->withSelector(list) }; + + } + // EO extendPseudo + + // ########################################################################## + // Rotates the element in list from [start] (inclusive) to [end] (exclusive) + // one index higher, looping the final element back to [start]. + // ########################################################################## + void Extender::rotateSlice( + std::vector& list, + size_t start, size_t end) + { + auto element = list[end - 1]; + for (size_t i = start; i < end; i++) { + auto next = list[i]; + list[i] = element; + element = next; + } + } + // EO rotateSlice + + // ########################################################################## + // Removes elements from [selectors] if they're subselectors of other + // elements. The [isOriginal] callback indicates which selectors are + // original to the document, and thus should never be trimmed. + // ########################################################################## + // Note: for adaption I pass in the set directly, there is some + // code path in selector-trim that might need this special callback + // ########################################################################## + std::vector Extender::trim( + const std::vector& selectors, + const ExtCplxSelSet& existing) const + { + + // Avoid truly horrific quadratic behavior. + // TODO(nweiz): I think there may be a way to get perfect trimming + // without going quadratic by building some sort of trie-like + // data structure that can be used to look up superselectors. + // TODO(mgreter): Check how this perfoms in C++ (up the limit) + if (selectors.size() > 100) return selectors; + + // This is n² on the sequences, but only comparing between separate sequences + // should limit the quadratic behavior. We iterate from last to first and reverse + // the result so that, if two selectors are identical, we keep the first one. + std::vector result; size_t numOriginals = 0; + + size_t i = selectors.size(); + outer: // Use label to continue loop + while (--i != std::string::npos) { + + const ComplexSelectorObj& complex1 = selectors[i]; + // Check if selector in known in existing "originals" + // For custom behavior dart-sass had `isOriginal(complex1)` + if (existing.find(complex1) != existing.end()) { + // Make sure we don't include duplicate originals, which could + // happen if a style rule extends a component of its own selector. + for (size_t j = 0; j < numOriginals; j++) { + if (ObjEqualityFn(result[j], complex1)) { + rotateSlice(result, 0, j + 1); + goto outer; + } + } + result.insert(result.begin(), complex1); + numOriginals++; + continue; + } + + // The maximum specificity of the sources that caused [complex1] + // to be generated. In order for [complex1] to be removed, there + // must be another selector that's a superselector of it *and* + // that has specificity greater or equal to this. + size_t maxSpecificity = 0; + for (const SelectorComponentObj& component : complex1->elements()) { + if (const CompoundSelectorObj compound = Cast(component)) { + maxSpecificity = std::max(maxSpecificity, maxSourceSpecificity(compound)); + } + } + + + // Look in [result] rather than [selectors] for selectors after [i]. This + // ensures we aren't comparing against a selector that's already been trimmed, + // and thus that if there are two identical selectors only one is trimmed. + if (hasAny(result, dontTrimComplex, complex1, maxSpecificity)) { + continue; + } + + // Check if any element (up to [i]) from [selector] returns true + // when passed to [dontTrimComplex]. The arguments [complex1] and + // [maxSepcificity] will be passed to the invoked function. + if (hasSubAny(selectors, i, dontTrimComplex, complex1, maxSpecificity)) { + continue; + } + + // ToDo: Maybe use deque for front insert? + result.insert(result.begin(), complex1); + + } + + return result; + + } + // EO trim + + // ########################################################################## + // Returns the maximum specificity of the given [simple] source selector. + // ########################################################################## + size_t Extender::maxSourceSpecificity(const SimpleSelectorObj& simple) const + { + auto it = sourceSpecificity.find(simple); + if (it == sourceSpecificity.end()) return 0; + return it->second; + } + // EO maxSourceSpecificity(SimpleSelectorObj) + + // ########################################################################## + // Returns the maximum specificity for sources that went into producing [compound]. + // ########################################################################## + size_t Extender::maxSourceSpecificity(const CompoundSelectorObj& compound) const + { + size_t specificity = 0; + for (auto simple : compound->elements()) { + size_t src = maxSourceSpecificity(simple); + specificity = std::max(specificity, src); + } + return specificity; + } + // EO maxSourceSpecificity(CompoundSelectorObj) + + // ########################################################################## + // Helper function used as callbacks on lists + // ########################################################################## + bool Extender::dontTrimComplex( + const ComplexSelector* complex2, + const ComplexSelector* complex1, + const size_t maxSpecificity) + { + if (complex2->minSpecificity() < maxSpecificity) return false; + return complex2->isSuperselectorOf(complex1); + } + // EO dontTrimComplex + + // ########################################################################## + // Helper function used as callbacks on lists + // ########################################################################## + bool Extender::hasExactlyOne(const ComplexSelectorObj& vec) + { + return vec->length() == 1; + } + // EO hasExactlyOne + + // ########################################################################## + // Helper function used as callbacks on lists + // ########################################################################## + bool Extender::hasMoreThanOne(const ComplexSelectorObj& vec) + { + return vec->length() > 1; + } + // hasMoreThanOne + +} diff --git a/src/libsass/src/extender.hpp b/src/libsass/src/extender.hpp new file mode 100644 index 000000000..5f161786a --- /dev/null +++ b/src/libsass/src/extender.hpp @@ -0,0 +1,399 @@ +#ifndef SASS_EXTENDER_H +#define SASS_EXTENDER_H + +#include +#include +#include + +#include "ast_helpers.hpp" +#include "ast_fwd_decl.hpp" +#include "operation.hpp" +#include "extension.hpp" +#include "backtrace.hpp" +#include "ordered_map.hpp" + +namespace Sass { + + // ########################################################################## + // Different hash map types used by extender + // ########################################################################## + + // This is special (ptrs!) + typedef std::unordered_set< + ComplexSelectorObj, + ObjPtrHash, + ObjPtrEquality + > ExtCplxSelSet; + + typedef std::unordered_set< + SimpleSelectorObj, + ObjHash, + ObjEquality + > ExtSmplSelSet; + + typedef std::unordered_set< + SelectorListObj, + ObjPtrHash, + ObjPtrEquality + > ExtListSelSet; + + typedef std::unordered_map< + SimpleSelectorObj, + ExtListSelSet, + ObjHash, + ObjEquality + > ExtSelMap; + + typedef ordered_map< + ComplexSelectorObj, + Extension, + ObjHash, + ObjEquality + > ExtSelExtMapEntry; + + typedef std::unordered_map< + SimpleSelectorObj, + ExtSelExtMapEntry, + ObjHash, + ObjEquality + > ExtSelExtMap; + + typedef std::unordered_map < + SimpleSelectorObj, + std::vector< + Extension + >, + ObjHash, + ObjEquality + > ExtByExtMap; + + class Extender : public Operation_CRTP { + + public: + + enum ExtendMode { TARGETS, REPLACE, NORMAL, }; + + private: + + // ########################################################################## + // The mode that controls this extender's behavior. + // ########################################################################## + ExtendMode mode; + + // ########################################################################## + // Shared backtraces with context and expander. Needed the throw + // errors when e.g. extending across media query boundaries. + // ########################################################################## + Backtraces& traces; + + // ########################################################################## + // A map from all simple selectors in the stylesheet to the rules that + // contain them.This is used to find which rules an `@extend` applies to. + // ########################################################################## + ExtSelMap selectors; + + // ########################################################################## + // A map from all extended simple selectors + // to the sources of those extensions. + // ########################################################################## + ExtSelExtMap extensions; + + // ########################################################################## + // A map from all simple selectors in extenders to + // the extensions that those extenders define. + // ########################################################################## + ExtByExtMap extensionsByExtender; + + // ########################################################################## + // A map from CSS rules to the media query contexts they're defined in. + // This tracks the contexts in which each style rule is defined. + // If a rule is defined at the top level, it doesn't have an entry. + // ########################################################################## + ordered_map< + SelectorListObj, + CssMediaRuleObj, + ObjPtrHash, + ObjPtrEquality + > mediaContexts; + + // ########################################################################## + // A map from [SimpleSelector]s to the specificity of their source selectors. + // This tracks the maximum specificity of the [ComplexSelector] that originally + // contained each [SimpleSelector]. This allows us to ensure we don't trim any + // selectors that need to exist to satisfy the [second law that of extend][]. + // [second law of extend]: https://github.com/sass/sass/issues/324#issuecomment-4607184 + // ########################################################################## + std::unordered_map< + SimpleSelectorObj, + size_t, + ObjPtrHash, + ObjPtrEquality + > sourceSpecificity; + + // ########################################################################## + // A set of [ComplexSelector]s that were originally part of their + // component [SelectorList]s, as opposed to being added by `@extend`. + // This allows us to ensure that we don't trim any selectors + // that need to exist to satisfy the [first law of extend][]. + // ########################################################################## + ExtCplxSelSet originals; + + public: + + // Constructor without default [mode]. + // [traces] are needed to throw errors. + Extender(Backtraces& traces); + + // ########################################################################## + // Constructor with specific [mode]. + // [traces] are needed to throw errors. + // ########################################################################## + Extender(ExtendMode mode, Backtraces& traces); + + // ########################################################################## + // Empty desctructor + // ########################################################################## + ~Extender() {}; + + // ########################################################################## + // Extends [selector] with [source] extender and [targets] extendees. + // This works as though `source {@extend target}` were written in the + // stylesheet, with the exception that [target] can contain compound + // selectors which must be extended as a unit. + // ########################################################################## + static SelectorListObj extend( + SelectorListObj& selector, + const SelectorListObj& source, + const SelectorListObj& target, + 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); + + // ########################################################################## + // Adds [selector] to this extender, with [selectorSpan] as the span covering + // the selector and [ruleSpan] as the span covering the entire style rule. + // Extends [selector] using any registered extensions, then returns an empty + // [ModifiableCssStyleRule] with the resulting selector. If any more relevant + // extensions are added, the returned rule is automatically updated. + // The [mediaContext] is the media query context in which the selector was + // defined, or `null` if it was defined at the top level of the document. + // ########################################################################## + void addSelector( + const SelectorListObj& selector, + const CssMediaRuleObj& mediaContext); + + // ########################################################################## + // Registers the [SimpleSelector]s in [list] + // to point to [rule] in [selectors]. + // ########################################################################## + void registerSelector( + const SelectorListObj& list, + const SelectorListObj& rule); + + // ########################################################################## + // Adds an extension to this extender. The [extender] is the selector for the + // style rule in which the extension is defined, and [target] is the selector + // passed to `@extend`. The [extend] provides the extend span and indicates + // whether the extension is optional. The [mediaContext] defines the media query + // context in which the extension is defined. It can only extend selectors + // within the same context. A `null` context indicates no media queries. + // ########################################################################## + void addExtension( + 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); + + // ########################################################################## + // 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( + // Taking in a reference here makes MSVC debug stuck!? + const std::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]. + // ########################################################################## + std::vector extendComplex( + // Taking in a reference here makes MSVC debug stuck!? + const ComplexSelectorObj& list, + const ExtSelExtMap& extensions, + const CssMediaRuleObj& mediaQueryContext); + + // ########################################################################## + // Returns a one-off [Extension] whose + // extender is composed solely of [simple]. + // ########################################################################## + Extension extensionForSimple( + const SimpleSelectorObj& simple) const; + + // ########################################################################## + // Returns a one-off [Extension] whose extender is composed + // solely of a compound selector containing [simples]. + // ########################################################################## + Extension extensionForCompound( + // Taking in a reference here makes MSVC debug stuck!? + const std::vector& simples) const; + + // ########################################################################## + // Extends [compound] using [extensions], and returns the + // contents of a [SelectorList]. The [inOriginal] parameter + // indicates whether this is in an original complex selector, + // meaning that [compound] should not be trimmed out. + // ########################################################################## + std::vector extendCompound( + const CompoundSelectorObj& compound, + const ExtSelExtMap& extensions, + const CssMediaRuleObj& mediaQueryContext, + bool inOriginal = false); + + // ########################################################################## + // Extends [simple] without extending the + // contents of any selector pseudos it contains. + // ########################################################################## + std::vector extendWithoutPseudo( + const SimpleSelectorObj& simple, + const ExtSelExtMap& extensions, + ExtSmplSelSet* targetsUsed) const; + + // ########################################################################## + // Extends [simple] and also extending the + // contents of any selector pseudos it contains. + // ########################################################################## + std::vector> extendSimple( + const SimpleSelectorObj& simple, + const ExtSelExtMap& extensions, + const CssMediaRuleObj& mediaQueryContext, + ExtSmplSelSet* targetsUsed); + + // ########################################################################## + // Inner loop helper for [extendPseudo] function + // ########################################################################## + static std::vector extendPseudoComplex( + const ComplexSelectorObj& complex, + const Pseudo_Selector_Obj& pseudo, + const CssMediaRuleObj& mediaQueryContext); + + // ########################################################################## + // Extends [pseudo] using [extensions], and returns + // a list of resulting pseudo selectors. + // ########################################################################## + std::vector extendPseudo( + const Pseudo_Selector_Obj& pseudo, + const ExtSelExtMap& extensions, + const CssMediaRuleObj& mediaQueryContext); + + // ########################################################################## + // Rotates the element in list from [start] (inclusive) to [end] (exclusive) + // one index higher, looping the final element back to [start]. + // ########################################################################## + static void rotateSlice( + std::vector& list, + size_t start, size_t end); + + // ########################################################################## + // Removes elements from [selectors] if they're subselectors of other + // elements. The [isOriginal] callback indicates which selectors are + // original to the document, and thus should never be trimmed. + // ########################################################################## + std::vector trim( + const std::vector& selectors, + const ExtCplxSelSet& set) const; + + // ########################################################################## + // Returns the maximum specificity of the given [simple] source selector. + // ########################################################################## + size_t maxSourceSpecificity(const SimpleSelectorObj& simple) const; + + // ########################################################################## + // Returns the maximum specificity for sources that went into producing [compound]. + // ########################################################################## + size_t maxSourceSpecificity(const CompoundSelectorObj& compound) const; + + // ########################################################################## + // Helper function used as callbacks on lists + // ########################################################################## + static bool dontTrimComplex( + const ComplexSelector* complex2, + const ComplexSelector* complex1, + const size_t maxSpecificity); + + // ########################################################################## + // Helper function used as callbacks on lists + // ########################################################################## + static bool hasExactlyOne(const ComplexSelectorObj& vec); + static bool hasMoreThanOne(const ComplexSelectorObj& vec); + + }; + +} + +#endif diff --git a/src/libsass/src/extension.cpp b/src/libsass/src/extension.cpp new file mode 100644 index 000000000..80f5f4130 --- /dev/null +++ b/src/libsass/src/extension.cpp @@ -0,0 +1,43 @@ +// sass.hpp must go before all system headers to get the +// __EXTENSIONS__ fix on Solaris. +#include "sass.hpp" + +#include "ast_helpers.hpp" +#include "extension.hpp" +#include "ast.hpp" + +namespace Sass { + + // ########################################################################## + // Static function to create a copy with a new extender + // ########################################################################## + Extension Extension::withExtender(const ComplexSelectorObj& newExtender) const + { + Extension extension(newExtender); + extension.specificity = specificity; + extension.isOptional = isOptional; + extension.target = target; + return extension; + } + + // ########################################################################## + // Asserts that the [mediaContext] for a selector is + // compatible with the query context for this extender. + // ########################################################################## + void Extension::assertCompatibleMediaContext(CssMediaRuleObj mediaQueryContext, Backtraces& traces) const + { + + if (this->mediaContext.isNull()) return; + + if (mediaQueryContext && ObjPtrEqualityFn(mediaContext->block(), mediaQueryContext->block())) return; + + if (ObjEqualityFn(mediaQueryContext, mediaContext)) return; + + throw Exception::ExtendAcrossMedia(traces, *this); + + } + + // ########################################################################## + // ########################################################################## + +} diff --git a/src/libsass/src/extension.hpp b/src/libsass/src/extension.hpp new file mode 100644 index 000000000..58fd5f821 --- /dev/null +++ b/src/libsass/src/extension.hpp @@ -0,0 +1,89 @@ +#ifndef SASS_EXTENSION_H +#define SASS_EXTENSION_H + +// sass.hpp must go before all system headers to get the +// __EXTENSIONS__ fix on Solaris. +#include "sass.hpp" + +#include +#include +#include "ast_fwd_decl.hpp" +#include "backtrace.hpp" + +namespace Sass { + + class Extension { + + public: + + // The selector in which the `@extend` appeared. + ComplexSelectorObj extender; + + // The selector that's being extended. + // `null` for one-off extensions. + SimpleSelectorObj target; + + // The minimum specificity required for any + // selector generated from this extender. + size_t specificity; + + // Whether this extension is optional. + bool isOptional; + + // Whether this is a one-off extender representing a selector that was + // originally in the document, rather than one defined with `@extend`. + bool isOriginal; + + bool isSatisfied; + + // The media query context to which this extend is restricted, + // or `null` if it can apply within any context. + CssMediaRuleObj mediaContext; + + // Creates a one-off extension that's not intended to be modified over time. + // If [specificity] isn't passed, it defaults to `extender.maxSpecificity`. + Extension(ComplexSelectorObj extender) : + extender(extender), + target({}), + specificity(0), + isOptional(true), + isOriginal(false), + isSatisfied(false), + mediaContext({}) { + + } + + // Copy constructor + Extension(const Extension& extension) : + extender(extension.extender), + target(extension.target), + specificity(extension.specificity), + isOptional(extension.isOptional), + isOriginal(extension.isOriginal), + isSatisfied(extension.isSatisfied), + mediaContext(extension.mediaContext) { + + } + + // Default constructor + Extension() : + extender({}), + target({}), + specificity(0), + isOptional(false), + isOriginal(false), + isSatisfied(false), + mediaContext({}) { + } + + // Asserts that the [mediaContext] for a selector is + // compatible with the query context for this extender. + void assertCompatibleMediaContext(CssMediaRuleObj mediaContext, Backtraces& traces) const; + + Extension withExtender(const ComplexSelectorObj& newExtender) const; + + }; + +} + +#endif diff --git a/src/libsass/src/file.cpp b/src/libsass/src/file.cpp index 32d4a7c63..2842ed3ef 100644 --- a/src/libsass/src/file.cpp +++ b/src/libsass/src/file.cpp @@ -1,4 +1,7 @@ +// sass.hpp must go before all system headers to get the +// __EXTENSIONS__ fix on Solaris. #include "sass.hpp" + #ifdef _WIN32 # ifdef __MINGW32__ # ifndef off64_t @@ -10,9 +13,7 @@ #else # include #endif -#include -#include -#include +#include #include #include #include @@ -21,6 +22,9 @@ #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" #ifdef _WIN32 @@ -79,7 +83,10 @@ namespace Sass { wchar_t resolved[32768]; // windows unicode filepaths are encoded in utf16 std::string abspath(join_paths(get_cwd(), path)); - std::wstring wpath(UTF_8::convert_to_utf16("\\\\?\\" + abspath)); + if (!(abspath[0] == '/' && abspath[1] == '/')) { + abspath = "//?/" + abspath; + } + std::wstring wpath(UTF_8::convert_to_utf16(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"); @@ -99,19 +106,19 @@ namespace Sass { bool is_absolute_path(const std::string& path) { #ifdef _WIN32 - if (path.length() >= 2 && isalpha(path[0]) && path[1] == ':') return true; + if (path.length() >= 2 && Util::ascii_isalpha(path[0]) && path[1] == ':') return true; #endif size_t i = 0; // check if we have a protocol - if (path[i] && Prelexer::is_alpha(path[i])) { + if (path[i] && Util::ascii_isalpha(static_cast(path[i]))) { // skip over all alphanumeric characters - while (path[i] && Prelexer::is_alnum(path[i])) ++i; + while (path[i] && Util::ascii_isalnum(static_cast(path[i]))) ++i; i = i && path[i] == ':' ? i + 1 : 0; } return path[i] == '/'; } - // helper function to find the last directory seperator + // helper function to find the last directory separator inline size_t find_last_folder_separator(const std::string& path, size_t limit = std::string::npos) { size_t pos; @@ -166,15 +173,15 @@ namespace Sass { while((pos = path.find("/./", pos)) != std::string::npos) path.erase(pos, 2); // remove all leading and trailing self references - while(path.length() > 1 && path.substr(0, 2) == "./") path.erase(0, 2); - while((pos = path.length()) > 1 && path.substr(pos - 2) == "/.") path.erase(pos - 2); + 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] && Prelexer::is_alpha(path[proto])) { + if (path[proto] && Util::ascii_isalpha(static_cast(path[proto]))) { // skip over all alphanumeric characters - while (path[proto] && Prelexer::is_alnum(path[proto++])) {} + while (path[proto] && Util::ascii_isalnum(static_cast(path[proto++]))) {} // then skip over the mandatory colon if (proto && path[proto] == ':') ++ proto; } @@ -253,9 +260,9 @@ namespace Sass { size_t proto = 0; // check if we have a protocol - if (path[proto] && Prelexer::is_alpha(path[proto])) { + if (path[proto] && Util::ascii_isalpha(static_cast(path[proto]))) { // skip over all alphanumeric characters - while (path[proto] && Prelexer::is_alnum(path[proto++])) {} + while (path[proto] && Util::ascii_isalnum(static_cast(path[proto++]))) {} // then skip over the mandatory colon if (proto && path[proto] == ':') ++ proto; } @@ -281,7 +288,8 @@ namespace Sass { #else // compare the charactes in a case insensitive manner // windows fs is only case insensitive in ascii ranges - if (tolower(abs_path[i]) != tolower(abs_base[i])) break; + if (Util::ascii_tolower(static_cast(abs_path[i])) != + Util::ascii_tolower(static_cast(abs_base[i]))) break; #endif if (abs_path[i] == '/') index = i + 1; } @@ -323,6 +331,8 @@ namespace Sass { // (2) underscore + given // (3) underscore + given + extension // (4) given + extension + // (5) given + _index.scss + // (6) given + _index.sass std::vector resolve_includes(const std::string& root, const std::string& file, const std::vector& exts) { std::string filename = join_paths(root, file); @@ -350,6 +360,25 @@ namespace Sass { 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 }); + } + } // nothing found return includes; } @@ -411,7 +440,10 @@ namespace Sass { wchar_t resolved[32768]; // windows unicode filepaths are encoded in utf16 std::string abspath(join_paths(get_cwd(), path)); - std::wstring wpath(UTF_8::convert_to_utf16("\\\\?\\" + abspath)); + if (!(abspath[0] == '/' && abspath[1] == '/')) { + abspath = "//?/" + abspath; + } + std::wstring wpath(UTF_8::convert_to_utf16(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"); @@ -430,28 +462,34 @@ namespace Sass { // 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. + // See: + // https://www.spinics.net/lists/gcchelp/msg46851.html + // https://github.com/sass/sassc-ruby/issues/128 struct stat st; if (stat(path.c_str(), &st) == -1 || S_ISDIR(st.st_mode)) return 0; - std::ifstream file(path.c_str(), std::ios::in | std::ios::binary | std::ios::ate); - char* contents = 0; - if (file.is_open()) { - size_t size = file.tellg(); - // allocate an extra byte for the null char - // and another one for edge-cases in lexer - contents = (char*) malloc((size+2)*sizeof(char)); - file.seekg(0, std::ios::beg); - file.read(contents, size); - contents[size+0] = '\0'; - contents[size+1] = '\0'; - file.close(); + 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))); + if (std::fread(static_cast(contents), 1, size, fd) != size) { + free(contents); + std::fclose(fd); + return nullptr; + } + if (std::fclose(fd) != 0) { + free(contents); + return nullptr; } + contents[size] = '\0'; + contents[size + 1] = '\0'; #endif std::string extension; if (path.length() > 5) { extension = path.substr(path.length() - 5, 5); } - for(size_t i=0; i #include @@ -100,7 +104,7 @@ namespace Sass { public: // the file contents char* contents; - // conected sourcemap + // connected sourcemap char* srcmap; public: Resource(char* contents, char* srcmap) @@ -108,17 +112,6 @@ namespace Sass { { } }; - // parsed stylesheet from loaded resource - class StyleSheet : public Resource { - public: - // parsed root block - Block_Obj root; - public: - StyleSheet(const Resource& res, Block_Obj root) - : Resource(res), root(root) - { } - }; - namespace File { static std::vector defaultExtensions = { ".scss", ".sass", ".css" }; diff --git a/src/libsass/src/fn_colors.cpp b/src/libsass/src/fn_colors.cpp new file mode 100644 index 000000000..5e14db633 --- /dev/null +++ b/src/libsass/src/fn_colors.cpp @@ -0,0 +1,596 @@ +// sass.hpp must go before all system headers to get the +// __EXTENSIONS__ fix on Solaris. +#include "sass.hpp" + +#include +#include "ast.hpp" +#include "fn_utils.hpp" +#include "fn_colors.hpp" +#include "util.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 std::string& str = s->value(); + return starts_with(str, "calc(") || + starts_with(str, "var("); + } + + void hsla_alpha_percent_deprecation(const ParserState& pstate, const std::string val) + { + + std::string msg("Passing a percentage as the alpha value to hsla() will be interpreted"); + std::string tail("differently in future versions of Sass. For now, use " + val + " instead."); + + deprecated(msg, tail, false, pstate); + + } + + Signature rgb_sig = "rgb($red, $green, $blue)"; + BUILT_IN(rgb) + { + 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")); + } + + Signature rgba_4_sig = "rgba($red, $green, $blue, $alpha)"; + BUILT_IN(rgba_4) + { + 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")); + } + + Signature rgba_2_sig = "rgba($color, $alpha)"; + BUILT_IN(rgba_2) + { + 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"]) + ) { + std::stringstream 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(); + } + + //////////////// + // RGB FUNCTIONS + //////////////// + + Signature red_sig = "red($color)"; + BUILT_IN(red) + { + Color_RGBA_Obj color = ARG("$color", Color)->toRGBA(); + return SASS_MEMORY_NEW(Number, pstate, color->r()); + } + + Signature green_sig = "green($color)"; + BUILT_IN(green) + { + Color_RGBA_Obj color = ARG("$color", Color)->toRGBA(); + return SASS_MEMORY_NEW(Number, pstate, color->g()); + } + + Signature blue_sig = "blue($color)"; + BUILT_IN(blue) + { + Color_RGBA_Obj color = ARG("$color", Color)->toRGBA(); + return SASS_MEMORY_NEW(Number, pstate, color->b()); + } + + Color_RGBA* colormix(Context& ctx, ParserState& 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)); + } + + Signature mix_sig = "mix($color1, $color2, $weight: 50%)"; + BUILT_IN(mix) + { + 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); + + } + + //////////////// + // HSL FUNCTIONS + //////////////// + + Signature hsl_sig = "hsl($hue, $saturation, $lightness)"; + BUILT_IN(hsl) + { + 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); + + } + + Signature hsla_sig = "hsla($hue, $saturation, $lightness, $alpha)"; + BUILT_IN(hsla) + { + 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); + std::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")); + + } + + ///////////////////////////////////////////////////////////////////////// + // Query functions + ///////////////////////////////////////////////////////////////////////// + + Signature hue_sig = "hue($color)"; + BUILT_IN(hue) + { + Color_HSLA_Obj col = ARG("$color", Color)->toHSLA(); + return SASS_MEMORY_NEW(Number, pstate, col->h(), "deg"); + } + + Signature saturation_sig = "saturation($color)"; + BUILT_IN(saturation) + { + Color_HSLA_Obj col = ARG("$color", Color)->toHSLA(); + return SASS_MEMORY_NEW(Number, pstate, col->s(), "%"); + } + + Signature lightness_sig = "lightness($color)"; + BUILT_IN(lightness) + { + Color_HSLA_Obj col = ARG("$color", Color)->toHSLA(); + return SASS_MEMORY_NEW(Number, pstate, col->l(), "%"); + } + + ///////////////////////////////////////////////////////////////////////// + // 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(); + } + + 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(); + + } + + 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(); + } + + 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) + ")"); + } + + 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 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) + ")"); + } + + Color* col = ARG("$color", Color); + Color_HSLA_Obj copy = col->copyAsHSLA(); + copy->s(0.0); // just reset saturation + return copy.detach(); + } + + ///////////////////////////////////////////////////////////////////////// + // Misc manipulation functions + ///////////////////////////////////////////////////////////////////////// + + 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(); + } + + 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 + ///////////////////////////////////////////////////////////////////////// + + 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() + ")"); + } + + // 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) + ")"); + } + + return SASS_MEMORY_NEW(Number, pstate, ARG("$color", Color)->a()); + } + + 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(); + } + + //////////////////////// + // OTHER COLOR FUNCTIONS + //////////////////////// + + 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) + { + 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; + } + + 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) + { + 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; + } + + Signature ie_hex_str_sig = "ie-hex-str($color)"; + BUILT_IN(ie_hex_str) + { + 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; + + std::stringstream 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)); + + std::string result = ss.str(); + Util::ascii_str_toupper(&result); + return SASS_MEMORY_NEW(String_Quoted, pstate, result); + } + + } + +} diff --git a/src/libsass/src/fn_colors.hpp b/src/libsass/src/fn_colors.hpp new file mode 100644 index 000000000..a474c64b5 --- /dev/null +++ b/src/libsass/src/fn_colors.hpp @@ -0,0 +1,85 @@ +#ifndef SASS_FN_COLORS_H +#define SASS_FN_COLORS_H + +#include "fn_utils.hpp" + +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); + + } + +} + +#endif diff --git a/src/libsass/src/fn_lists.cpp b/src/libsass/src/fn_lists.cpp new file mode 100644 index 000000000..93f3252ef --- /dev/null +++ b/src/libsass/src/fn_lists.cpp @@ -0,0 +1,285 @@ +// 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" + +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) { + Expression_Obj obj = arglist->at(i); + Argument_Obj arg = (Argument*) obj.ptr(); // XXX + std::string name = std::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(); + } + + 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); + } + } + + 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 `" + std::string(sig) + "` must not be empty", pstate, traces); + double index = std::floor(nr < 0 ? len + nr : nr - 1); + if (index < 0 || index > len - 1) error("index out of bounds for `" + std::string(sig) + "`", pstate, traces); + return Cast(Listize::perform(sl->get(static_cast(index)))); + } + List_Obj l = Cast(env["$list"]); + if (nr == 0) error("argument `$n` of `" + std::string(sig) + "` must be non-zero", pstate, traces); + // 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 `" + std::string(sig) + "` must not be empty", pstate, traces); + double index = std::floor(nr < 0 ? len + nr : nr - 1); + if (index < 0 || index > len - 1) error("index out of bounds for `" + std::string(sig) + "`", pstate, traces); + + 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 { + Value_Obj 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); + Expression_Obj 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 `" + std::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 `" + std::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"]); + Expression_Obj 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); + } + + 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(); + std::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 `" + std::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(); + } + + Signature append_sig = "append($list, $val, $separator: auto)"; + BUILT_IN(append) + { + Map_Obj m = Cast(env["$list"]); + List_Obj l = Cast(env["$list"]); + Expression_Obj 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); + std::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 `" + std::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; + } + + 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)); + } + if (arglist->is_arglist()) { + Argument_Obj arg = (Argument*)(arglist->at(i).ptr()); // XXX + arg->value(ith); + } else { + (*arglist)[i] = ith; + } + } + shortest = (i ? std::min(shortest, ith->length()) : ith->length()); + } + 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)); + } + zippers->append(zipper); + } + 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)); + } + return SASS_MEMORY_NEW(String_Quoted, + pstate, + l->separator() == SASS_COMMA ? "comma" : "space"); + } + + Signature is_bracketed_sig = "is-bracketed($list)"; + BUILT_IN(is_bracketed) + { + Value_Obj value = ARG("$list", Value); + List_Obj list = Cast(value); + return SASS_MEMORY_NEW(Boolean, pstate, list && list->is_bracketed()); + } + + } + +} diff --git a/src/libsass/src/fn_lists.hpp b/src/libsass/src/fn_lists.hpp new file mode 100644 index 000000000..260023aeb --- /dev/null +++ b/src/libsass/src/fn_lists.hpp @@ -0,0 +1,34 @@ +#ifndef SASS_FN_LISTS_H +#define SASS_FN_LISTS_H + +#include "fn_utils.hpp" + +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); + + } + +} + +#endif diff --git a/src/libsass/src/fn_maps.cpp b/src/libsass/src/fn_maps.cpp new file mode 100644 index 000000000..ae54783e2 --- /dev/null +++ b/src/libsass/src/fn_maps.cpp @@ -0,0 +1,94 @@ +#include "operators.hpp" +#include "fn_utils.hpp" +#include "fn_maps.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); + Expression_Obj v = ARG("$key", Expression); + try { + Value_Obj 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&) { + 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); + Expression_Obj 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); + } + 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)); + } + 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; + } + + 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)); + } + if (!remove) *result << std::make_pair(key, m->at(key)); + } + return result; + } + + } + +} \ No newline at end of file diff --git a/src/libsass/src/fn_maps.hpp b/src/libsass/src/fn_maps.hpp new file mode 100644 index 000000000..9551ec33e --- /dev/null +++ b/src/libsass/src/fn_maps.hpp @@ -0,0 +1,30 @@ +#ifndef SASS_FN_MAPS_H +#define SASS_FN_MAPS_H + +#include "fn_utils.hpp" + +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); + + } + +} + +#endif diff --git a/src/libsass/src/fn_miscs.cpp b/src/libsass/src/fn_miscs.cpp new file mode 100644 index 000000000..e4c72dabc --- /dev/null +++ b/src/libsass/src/fn_miscs.cpp @@ -0,0 +1,244 @@ +#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) + { + std::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) + { + std::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); + } + + std::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) + { + std::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) + { + std::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) + { + std::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); + // std::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) { + Expression_Obj 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()) { + Expression_Obj 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); + Expression_Obj cond = ARG("$condition", Expression)->perform(&expand.eval); + bool is_true = !cond->is_false(); + Expression_Obj res = ARG(is_true ? "$if-true" : "$if-false", Expression); + Value_Obj 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); + } + + std::string name = Util::normalize_underscores(unquote(ss->value())); + std::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/libsass/src/fn_miscs.hpp b/src/libsass/src/fn_miscs.hpp new file mode 100644 index 000000000..aec693e92 --- /dev/null +++ b/src/libsass/src/fn_miscs.hpp @@ -0,0 +1,40 @@ +#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/libsass/src/fn_numbers.cpp b/src/libsass/src/fn_numbers.cpp new file mode 100644 index 000000000..5a585713d --- /dev/null +++ b/src/libsass/src/fn_numbers.cpp @@ -0,0 +1,227 @@ +// 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" +#include "fn_numbers.hpp" + +#ifdef __MINGW32__ +#include "windows.h" +#include "wincrypt.h" +#endif + +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)); + + return seed; + } + #else + uint64_t GetSeed() + { + std::random_device rd; + return rd(); + } + #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 `" + std::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(); + } + + 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(); + } + + 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); + } + for (size_t i = 0; i < L; ++i) { + Expression_Obj 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); + } + if (least) { + if (*xi < *least) least = xi; + } else least = xi; + } + 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) { + Expression_Obj 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); + } + if (greatest) { + if (*greatest < *xi) greatest = xi; + } else greatest = xi; + } + 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) { + std::stringstream err; + err << "$limit " << lv << " must be greater than or equal to 1 for `random'"; + error(err.str(), pstate, traces); + } + bool eq_int = std::fabs(trunc(lv) - lv) < NUMBER_EPSILON; + if (!eq_int) { + std::stringstream err; + err << "Expected $limit to be an integer but got " << lv << " for `random'"; + error(err.str(), pstate, traces); + } + std::uniform_real_distribution<> distributor(1, lv + 1); + uint_fast32_t distributed = static_cast(distributor(rand)); + return SASS_MEMORY_NEW(Number, pstate, (double)distributed); + } + 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"); + } + } + + Signature unique_id_sig = "unique-id()"; + BUILT_IN(unique_id) + { + std::stringstream 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"); + std::string str(quote(arg->unit(), '"')); + return SASS_MEMORY_NEW(String_Quoted, pstate, str); + } + + 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); + } + + 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); + } + // 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); + } + + } + +} diff --git a/src/libsass/src/fn_numbers.hpp b/src/libsass/src/fn_numbers.hpp new file mode 100644 index 000000000..dba96be0b --- /dev/null +++ b/src/libsass/src/fn_numbers.hpp @@ -0,0 +1,45 @@ +#ifndef SASS_FN_NUMBERS_H +#define SASS_FN_NUMBERS_H + +#include "fn_utils.hpp" + +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); + + } + +} + +#endif \ No newline at end of file diff --git a/src/libsass/src/fn_selectors.cpp b/src/libsass/src/fn_selectors.cpp new file mode 100644 index 000000000..34142bb81 --- /dev/null +++ b/src/libsass/src/fn_selectors.cpp @@ -0,0 +1,205 @@ +#include + +#include "parser.hpp" +#include "extender.hpp" +#include "listize.hpp" +#include "fn_utils.hpp" +#include "fn_selectors.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); + } + + // Parse args into vector of selectors + SelectorStack parsedSelectors; + for (size_t i = 0, L = arglist->length(); i < L; ++i) { + Expression_Obj exp = Cast(arglist->value_at_index(i)); + if (exp->concrete_type() == Expression::NULL_VAL) { + error( + "$selectors: null is not a valid selector: it must be a string,\n" + "a list of strings, or a list of lists of strings for 'selector-nest'", + pstate, traces); + } + if (String_Constant_Obj str = Cast(exp)) { + str->quote_mark(0); + } + std::string exp_src = exp->to_string(ctx.c_options); + SelectorListObj sel = Parser::parse_selector(exp_src.c_str(), ctx, traces); + parsedSelectors.push_back(sel); + } + + // Nothing to do + if( parsedSelectors.empty() ) { + return SASS_MEMORY_NEW(Null, pstate); + } + + // Set the first element as the `result`, keep + // appending to as we go down the parsedSelector vector. + SelectorStack::iterator itr = parsedSelectors.begin(); + 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(); + } + + 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); + } + std::string exp_src = exp->to_string(); + SelectorListObj sel = Parser::parse_selector(exp_src.c_str(), ctx, traces, + exp->pstate(), pstate.src, + /*allow_parent=*/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); + } + } + + if (parsedSelectors.size() > 1) { + + if (!sel->has_real_parent_ref()) { + auto parent = parsedSelectors.back(); + for (auto& complex : parent->elements()) { + if (CompoundSelector* comp = Cast(complex->first())) { + comp->hasRealParent(false); + } + } + 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); + } + } + + // Nothing to do + if( parsedSelectors.empty() ) { + return SASS_MEMORY_NEW(Null, pstate); + } + + return Cast(Listize::perform(parsedSelectors.back())); + } + + 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)); + } + + Signature simple_selectors_sig = "simple-selectors($selector)"; + BUILT_IN(simple_selectors) + { + CompoundSelectorObj sel = ARGSEL("$selector"); + + List* l = SASS_MEMORY_NEW(List, sel->pstate(), sel->length(), SASS_COMMA); + + for (size_t i = 0, L = sel->length(); i < L; ++i) { + const SimpleSelectorObj& ss = sel->get(i); + std::string ss_string = ss->to_string() ; + l->append(SASS_MEMORY_NEW(String_Quoted, ss->pstate(), ss_string)); + } + + 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)); + } + + 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/libsass/src/fn_selectors.hpp b/src/libsass/src/fn_selectors.hpp new file mode 100644 index 000000000..d5c106cd2 --- /dev/null +++ b/src/libsass/src/fn_selectors.hpp @@ -0,0 +1,35 @@ +#ifndef SASS_FN_SELECTORS_H +#define SASS_FN_SELECTORS_H + +#include "fn_utils.hpp" + +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; + + } + +} + +#endif diff --git a/src/libsass/src/fn_strings.cpp b/src/libsass/src/fn_strings.cpp new file mode 100644 index 000000000..79805d782 --- /dev/null +++ b/src/libsass/src/fn_strings.cpp @@ -0,0 +1,256 @@ +// 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 ParserState& pstate, Backtraces traces) + { + try { + throw; + } + catch (utf8::invalid_code_point&) { + std::string msg("utf8::invalid_code_point"); + error(msg, pstate, traces); + } + catch (utf8::not_enough_room&) { + std::string msg("utf8::not_enough_room"); + error(msg, pstate, traces); + } + catch (utf8::invalid_utf8&) { + std::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; + std::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 = std::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) + { + std::string str; + try { + String_Constant* s = ARG("$string", String_Constant); + str = s->value(); + String_Constant* i = ARG("$insert", String_Constant); + std::string ins = i->value(); + double index = ARGVAL("$index"); + if (index != (int)index) { + error("$index: " + std::to_string(index) + " is not an int", 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 = std::string::npos; + try { + String_Constant* s = ARG("$string", String_Constant); + String_Constant* t = ARG("$substring", String_Constant); + std::string str = s->value(); + std::string substr = t->value(); + + size_t c_index = str.find(substr); + if(c_index == std::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) + { + std::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) { + error("$start-at: " + std::to_string(start_at) + " is not an int", pstate, traces); + } + + String_Quoted* ss = Cast(s); + + std::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) { + error("$end-at: " + std::to_string(end_at) + " is not an int", 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) + { + std::string::iterator start = str.begin(); + utf8::advance(start, start_at - 1, str.end()); + std::string::iterator end = start; + utf8::advance(end, end_at - start_at + 1, str.end()); + newstr = std::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); + std::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); + std::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/libsass/src/fn_strings.hpp b/src/libsass/src/fn_strings.hpp new file mode 100644 index 000000000..4a1ed1900 --- /dev/null +++ b/src/libsass/src/fn_strings.hpp @@ -0,0 +1,34 @@ +#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/libsass/src/fn_utils.cpp b/src/libsass/src/fn_utils.cpp new file mode 100644 index 000000000..ae5f0ac96 --- /dev/null +++ b/src/libsass/src/fn_utils.cpp @@ -0,0 +1,155 @@ +// 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) + { + Parser sig_parser = Parser::from_c_str(sig, ctx, ctx.traces, ParserState("[built-in function]")); + sig_parser.lex(); + std::string name(Util::normalize_underscores(sig_parser.lexed)); + Parameters_Obj params = sig_parser.parse_parameters(); + return SASS_MEMORY_NEW(Definition, + ParserState("[built-in function]"), + 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); + Parser sig_parser = Parser::from_c_str(sig, ctx, ctx.traces, ParserState("[c function]")); + // 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 > + > >(); + std::string name(Util::normalize_underscores(sig_parser.lexed)); + Parameters_Obj params = sig_parser.parse_parameters(); + return SASS_MEMORY_NEW(Definition, + ParserState("[c function]"), + sig, + name, + params, + c_func); + } + + namespace Functions { + + std::string function_name(Signature sig) + { + std::string str(sig); + return str.substr(0, str.find('(')); + } + + Map* get_arg_m(const std::string& argname, Env& env, Signature sig, ParserState 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 std::string& argname, Env& env, Signature sig, ParserState 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)) { + std::stringstream 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 std::string& argname, Env& env, Signature sig, ParserState 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 std::string& argname, Env& env, Signature sig, ParserState pstate, Backtraces traces) + { + Number* val = get_arg(argname, env, sig, pstate, traces); + Number tmpnr(val); + tmpnr.reduce(); + return tmpnr.value(); + } + + double color_num(const std::string& argname, Env& env, Signature sig, ParserState 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 std::string& argname, Env& env, Signature sig, ParserState 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 std::string& argname, Env& env, Signature sig, ParserState pstate, Backtraces traces, Context& ctx) { + Expression_Obj exp = ARG(argname, Expression); + if (exp->concrete_type() == Expression::NULL_VAL) { + std::stringstream msg; + 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); + } + std::string exp_src = exp->to_string(ctx.c_options); + return Parser::parse_selector(exp_src.c_str(), ctx, traces, exp->pstate(), pstate.src, /*allow_parent=*/false); + } + + CompoundSelectorObj get_arg_sel(const std::string& argname, Env& env, Signature sig, ParserState pstate, Backtraces traces, Context& ctx) { + Expression_Obj exp = ARG(argname, Expression); + if (exp->concrete_type() == Expression::NULL_VAL) { + std::stringstream msg; + 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); + } + std::string exp_src = exp->to_string(ctx.c_options); + SelectorListObj sel_list = Parser::parse_selector(exp_src.c_str(), ctx, traces, exp->pstate(), pstate.src, /*allow_parent=*/false); + if (sel_list->length() == 0) return {}; + return sel_list->first()->first(); + } + + + } + +} diff --git a/src/libsass/src/fn_utils.hpp b/src/libsass/src/fn_utils.hpp new file mode 100644 index 000000000..85ce4bc1a --- /dev/null +++ b/src/libsass/src/fn_utils.hpp @@ -0,0 +1,62 @@ +#ifndef SASS_FN_UTILS_H +#define SASS_FN_UTILS_H + +// sass.hpp must go before all system headers to get the +// __EXTENSIONS__ fix on Solaris. +#include "sass.hpp" + +#include "units.hpp" +#include "backtrace.hpp" +#include "environment.hpp" +#include "ast_fwd_decl.hpp" +#include "error_handling.hpp" + +namespace Sass { + + #define FN_PROTOTYPE \ + Env& env, \ + Env& d_env, \ + Context& ctx, \ + Signature sig, \ + ParserState 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 std::string& argname, Env& env, Signature sig, ParserState 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; + } + + Map* get_arg_m(const std::string& argname, Env& env, Signature sig, ParserState pstate, Backtraces traces); // maps only + Number* get_arg_n(const std::string& argname, Env& env, Signature sig, ParserState pstate, Backtraces traces); // numbers only + double alpha_num(const std::string& argname, Env& env, Signature sig, ParserState pstate, Backtraces traces); // colors only + double color_num(const std::string& argname, Env& env, Signature sig, ParserState pstate, Backtraces traces); // colors only + double get_arg_r(const std::string& argname, Env& env, Signature sig, ParserState pstate, Backtraces traces, double lo, double hi); // colors only + double get_arg_val(const std::string& argname, Env& env, Signature sig, ParserState pstate, Backtraces traces); // shared + SelectorListObj get_arg_sels(const std::string& argname, Env& env, Signature sig, ParserState pstate, Backtraces traces, Context& ctx); // selectors only + CompoundSelectorObj get_arg_sel(const std::string& argname, Env& env, Signature sig, ParserState pstate, Backtraces traces, Context& ctx); // selectors only + + } + +} + +#endif diff --git a/src/libsass/src/functions.cpp b/src/libsass/src/functions.cpp deleted file mode 100644 index c9999fc3a..000000000 --- a/src/libsass/src/functions.cpp +++ /dev/null @@ -1,2234 +0,0 @@ -#include "sass.hpp" -#include "functions.hpp" -#include "ast.hpp" -#include "context.hpp" -#include "backtrace.hpp" -#include "parser.hpp" -#include "constants.hpp" -#include "inspect.hpp" -#include "extend.hpp" -#include "eval.hpp" -#include "util.hpp" -#include "expand.hpp" -#include "operators.hpp" -#include "utf8_string.hpp" -#include "sass/base.h" -#include "utf8.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#ifdef __MINGW32__ -#include "windows.h" -#include "wincrypt.h" -#endif - -#define ARG(argname, argtype) get_arg(argname, env, sig, pstate, traces) -#define ARGM(argname, argtype, ctx) get_arg_m(argname, env, sig, pstate, traces, ctx) - -// 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 - -// special function for weird hsla percent (10px == 10% == 10 != 0.1) -#define ARGVAL(argname) get_arg_val(argname, env, sig, pstate, traces) // double - -// 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 - -namespace Sass { - using std::stringstream; - using std::endl; - - Definition_Ptr make_native_function(Signature sig, Native_Function func, Context& ctx) - { - Parser sig_parser = Parser::from_c_str(sig, ctx, ctx.traces, ParserState("[built-in function]")); - sig_parser.lex(); - std::string name(Util::normalize_underscores(sig_parser.lexed)); - Parameters_Obj params = sig_parser.parse_parameters(); - return SASS_MEMORY_NEW(Definition, - ParserState("[built-in function]"), - sig, - name, - params, - func, - false); - } - - Definition_Ptr make_c_function(Sass_Function_Entry c_func, Context& ctx) - { - using namespace Prelexer; - - const char* sig = sass_function_get_signature(c_func); - Parser sig_parser = Parser::from_c_str(sig, ctx, ctx.traces, ParserState("[c function]")); - // 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 > - > >(); - std::string name(Util::normalize_underscores(sig_parser.lexed)); - Parameters_Obj params = sig_parser.parse_parameters(); - return SASS_MEMORY_NEW(Definition, - ParserState("[c function]"), - sig, - name, - params, - c_func, - false, true); - } - - std::string function_name(Signature sig) - { - std::string str(sig); - return str.substr(0, str.find('(')); - } - - namespace Functions { - - inline void handle_utf8_error (const ParserState& pstate, Backtraces traces) - { - try { - throw; - } - catch (utf8::invalid_code_point) { - std::string msg("utf8::invalid_code_point"); - error(msg, pstate, traces); - } - catch (utf8::not_enough_room) { - std::string msg("utf8::not_enough_room"); - error(msg, pstate, traces); - } - catch (utf8::invalid_utf8) { - std::string msg("utf8::invalid_utf8"); - error(msg, pstate, traces); - } - catch (...) { throw; } - } - - template - T* get_arg(const std::string& argname, Env& env, Signature sig, ParserState pstate, Backtraces traces) - { - // Minimal error handling -- the expectation is that built-ins will be written correctly! - T* val = Cast(env[argname]); - if (!val) { - std::string msg("argument `"); - msg += argname; - msg += "` of `"; - msg += sig; - msg += "` must be a "; - msg += T::type_name(); - error(msg, pstate, traces); - } - return val; - } - - Map_Ptr get_arg_m(const std::string& argname, Env& env, Signature sig, ParserState pstate, Backtraces traces, Context& ctx) - { - // Minimal error handling -- the expectation is that built-ins will be written correctly! - Map_Ptr val = Cast(env[argname]); - if (val) return val; - - List_Ptr lval = Cast(env[argname]); - if (lval && lval->length() == 0) return SASS_MEMORY_NEW(Map, pstate, 0); - - // fallback on get_arg for error handling - val = get_arg(argname, env, sig, pstate, traces); - return val; - } - - double get_arg_r(const std::string& argname, Env& env, Signature sig, ParserState pstate, Backtraces traces, double lo, double hi) - { - // Minimal error handling -- the expectation is that built-ins will be written correctly! - Number_Ptr val = get_arg(argname, env, sig, pstate, traces); - Number tmpnr(val); - tmpnr.reduce(); - double v = tmpnr.value(); - if (!(lo <= v && v <= hi)) { - std::stringstream msg; - msg << "argument `" << argname << "` of `" << sig << "` must be between "; - msg << lo << " and " << hi; - error(msg.str(), pstate, traces); - } - return v; - } - - Number_Ptr get_arg_n(const std::string& argname, Env& env, Signature sig, ParserState pstate, Backtraces traces) - { - // Minimal error handling -- the expectation is that built-ins will be written correctly! - Number_Ptr val = get_arg(argname, env, sig, pstate, traces); - val = SASS_MEMORY_COPY(val); - val->reduce(); - return val; - } - - double get_arg_v(const std::string& argname, Env& env, Signature sig, ParserState pstate, Backtraces traces) - { - // Minimal error handling -- the expectation is that built-ins will be written correctly! - Number_Ptr val = get_arg(argname, env, sig, pstate, traces); - Number tmpnr(val); - tmpnr.reduce(); - /* - if (tmpnr.unit() == "%") { - tmpnr.value(tmpnr.value() / 100); - tmpnr.numerators.clear(); - } else { - if (!tmpnr.is_unitless()) error("argument " + argname + " of `" + std::string(sig) + "` must be unitless", pstate); - } - */ - return tmpnr.value(); - } - - double get_arg_val(const std::string& argname, Env& env, Signature sig, ParserState pstate, Backtraces traces) - { - // Minimal error handling -- the expectation is that built-ins will be written correctly! - Number_Ptr val = get_arg(argname, env, sig, pstate, traces); - Number tmpnr(val); - tmpnr.reduce(); - return tmpnr.value(); - } - - double color_num(const std::string& argname, Env& env, Signature sig, ParserState pstate, Backtraces traces) - { - Number_Ptr 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); - } - } - - - inline double alpha_num(const std::string& argname, Env& env, Signature sig, ParserState pstate, Backtraces traces) { - Number_Ptr 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); - } - } - - #define ARGSEL(argname, seltype, contextualize) get_arg_sel(argname, env, sig, pstate, traces, ctx) - - template - T get_arg_sel(const std::string& argname, Env& env, Signature sig, ParserState pstate, Backtraces traces, Context& ctx); - - template <> - Selector_List_Obj get_arg_sel(const std::string& argname, Env& env, Signature sig, ParserState pstate, Backtraces traces, Context& ctx) { - Expression_Obj exp = ARG(argname, Expression); - if (exp->concrete_type() == Expression::NULL_VAL) { - std::stringstream msg; - 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(), pstate, traces); - } - if (String_Constant_Ptr str = Cast(exp)) { - str->quote_mark(0); - } - std::string exp_src = exp->to_string(ctx.c_options); - return Parser::parse_selector(exp_src.c_str(), ctx, traces); - } - - template <> - Compound_Selector_Obj get_arg_sel(const std::string& argname, Env& env, Signature sig, ParserState pstate, Backtraces traces, Context& ctx) { - Expression_Obj exp = ARG(argname, Expression); - if (exp->concrete_type() == Expression::NULL_VAL) { - std::stringstream msg; - msg << argname << ": null is not a string for `" << function_name(sig) << "'"; - error(msg.str(), pstate, traces); - } - if (String_Constant_Ptr str = Cast(exp)) { - str->quote_mark(0); - } - std::string exp_src = exp->to_string(ctx.c_options); - Selector_List_Obj sel_list = Parser::parse_selector(exp_src.c_str(), ctx, traces); - if (sel_list->length() == 0) return NULL; - Complex_Selector_Obj first = sel_list->first(); - if (!first->tail()) return first->head(); - return first->tail()->head(); - } - - #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)); - - return seed; - } - #else - uint64_t GetSeed() - { - std::random_device rd; - return rd(); - } - #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())); - - // features - static std::set features { - "global-variable-shadowing", - "extend-selector-pseudoclass", - "at-error", - "units-level-3", - "custom-property" - }; - - //////////////// - // RGB FUNCTIONS - //////////////// - - inline bool special_number(String_Constant_Ptr s) { - if (s) { - std::string calc("calc("); - std::string var("var("); - std::string ss(s->value()); - return std::equal(calc.begin(), calc.end(), ss.begin()) || - std::equal(var.begin(), var.end(), ss.begin()); - } - return false; - } - - Signature rgb_sig = "rgb($red, $green, $blue)"; - BUILT_IN(rgb) - { - if ( - special_number(Cast(env["$red"])) || - special_number(Cast(env["$green"])) || - special_number(Cast(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, - pstate, - COLOR_NUM("$red"), - COLOR_NUM("$green"), - COLOR_NUM("$blue")); - } - - Signature rgba_4_sig = "rgba($red, $green, $blue, $alpha)"; - BUILT_IN(rgba_4) - { - if ( - special_number(Cast(env["$red"])) || - special_number(Cast(env["$green"])) || - special_number(Cast(env["$blue"])) || - special_number(Cast(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, - pstate, - COLOR_NUM("$red"), - COLOR_NUM("$green"), - COLOR_NUM("$blue"), - ALPHA_NUM("$alpha")); - } - - Signature rgba_2_sig = "rgba($color, $alpha)"; - BUILT_IN(rgba_2) - { - if ( - special_number(Cast(env["$color"])) - ) { - return SASS_MEMORY_NEW(String_Constant, pstate, "rgba(" - + env["$color"]->to_string() - + ", " - + env["$alpha"]->to_string() - + ")" - ); - } - - Color_Ptr c_arg = ARG("$color", Color); - - if ( - special_number(Cast(env["$alpha"])) - ) { - std::stringstream 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_Ptr new_c = SASS_MEMORY_COPY(c_arg); - new_c->a(ALPHA_NUM("$alpha")); - new_c->disp(""); - return new_c; - } - - Signature red_sig = "red($color)"; - BUILT_IN(red) - { return SASS_MEMORY_NEW(Number, pstate, ARG("$color", Color)->r()); } - - Signature green_sig = "green($color)"; - BUILT_IN(green) - { return SASS_MEMORY_NEW(Number, pstate, ARG("$color", Color)->g()); } - - Signature blue_sig = "blue($color)"; - BUILT_IN(blue) - { return SASS_MEMORY_NEW(Number, pstate, ARG("$color", Color)->b()); } - - Color* colormix(Context& ctx, ParserState& pstate, Color* color1, Color* color2, double weight) { - double p = weight/100; - double w = 2*p - 1; - double a = color1->a() - color2->a(); - - double w1 = (((w * a == -1) ? w : (w + a)/(1 + w*a)) + 1)/2.0; - double w2 = 1 - w1; - - return SASS_MEMORY_NEW(Color, - pstate, - Sass::round(w1*color1->r() + w2*color2->r(), ctx.c_options.precision), - Sass::round(w1*color1->g() + w2*color2->g(), ctx.c_options.precision), - Sass::round(w1*color1->b() + w2*color2->b(), ctx.c_options.precision), - color1->a()*p + color2->a()*(1-p)); - } - - Signature mix_sig = "mix($color-1, $color-2, $weight: 50%)"; - BUILT_IN(mix) - { - Color_Obj color1 = ARG("$color-1", Color); - Color_Obj color2 = ARG("$color-2", Color); - double weight = DARG_U_PRCT("$weight"); - return colormix(ctx, pstate, color1, color2, weight); - - } - - //////////////// - // HSL FUNCTIONS - //////////////// - - // RGB to HSL helper function - struct HSL { double h; double s; double l; }; - HSL rgb_to_hsl(double r, double g, double b) - { - - // Algorithm from http://en.wikipedia.org/wiki/wHSL_and_HSV#Conversion_from_RGB_to_HSL_or_HSV - r /= 255.0; g /= 255.0; 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; - hsl_struct.h = h / 6 * 360; - hsl_struct.s = s * 100; - hsl_struct.l = l * 100; - - return hsl_struct; - } - - // hue to RGB helper function - double h_to_rgb(double m1, double m2, double h) { - while (h < 0) h += 1; - while (h > 1) h -= 1; - 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; - } - - Color_Ptr hsla_impl(double h, double s, double l, double a, Context& ctx, ParserState pstate) - { - h /= 360.0; - s /= 100.0; - l /= 100.0; - - if (l < 0) l = 0; - if (s < 0) s = 0; - if (l > 1) l = 1; - if (s > 1) s = 1; - while (h < 0) h += 1; - while (h > 1) h -= 1; - - // if saturation is exacly zero, we loose - // information for hue, since it will evaluate - // to zero if converted back from rgb. Setting - // saturation to a very tiny number solves this. - if (s == 0) s = 1e-10; - - // 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(Color, pstate, r, g, b, a); - } - - Signature hsl_sig = "hsl($hue, $saturation, $lightness)"; - BUILT_IN(hsl) - { - if ( - special_number(Cast(env["$hue"])) || - special_number(Cast(env["$saturation"])) || - special_number(Cast(env["$lightness"])) - ) { - return SASS_MEMORY_NEW(String_Constant, pstate, "hsl(" - + env["$hue"]->to_string() - + ", " - + env["$saturation"]->to_string() - + ", " - + env["$lightness"]->to_string() - + ")" - ); - } - - return hsla_impl(ARGVAL("$hue"), - ARGVAL("$saturation"), - ARGVAL("$lightness"), - 1.0, - ctx, - pstate); - } - - Signature hsla_sig = "hsla($hue, $saturation, $lightness, $alpha)"; - BUILT_IN(hsla) - { - if ( - special_number(Cast(env["$hue"])) || - special_number(Cast(env["$saturation"])) || - special_number(Cast(env["$lightness"])) || - special_number(Cast(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() - + ")" - ); - } - - return hsla_impl(ARGVAL("$hue"), - ARGVAL("$saturation"), - ARGVAL("$lightness"), - ARGVAL("$alpha"), - ctx, - pstate); - } - - Signature hue_sig = "hue($color)"; - BUILT_IN(hue) - { - Color_Ptr rgb_color = ARG("$color", Color); - HSL hsl_color = rgb_to_hsl(rgb_color->r(), - rgb_color->g(), - rgb_color->b()); - return SASS_MEMORY_NEW(Number, pstate, hsl_color.h, "deg"); - } - - Signature saturation_sig = "saturation($color)"; - BUILT_IN(saturation) - { - Color_Ptr rgb_color = ARG("$color", Color); - HSL hsl_color = rgb_to_hsl(rgb_color->r(), - rgb_color->g(), - rgb_color->b()); - return SASS_MEMORY_NEW(Number, pstate, hsl_color.s, "%"); - } - - Signature lightness_sig = "lightness($color)"; - BUILT_IN(lightness) - { - Color_Ptr rgb_color = ARG("$color", Color); - HSL hsl_color = rgb_to_hsl(rgb_color->r(), - rgb_color->g(), - rgb_color->b()); - return SASS_MEMORY_NEW(Number, pstate, hsl_color.l, "%"); - } - - Signature adjust_hue_sig = "adjust-hue($color, $degrees)"; - BUILT_IN(adjust_hue) - { - Color_Ptr rgb_color = ARG("$color", Color); - double degrees = ARGVAL("$degrees"); - HSL hsl_color = rgb_to_hsl(rgb_color->r(), - rgb_color->g(), - rgb_color->b()); - return hsla_impl(hsl_color.h + degrees, - hsl_color.s, - hsl_color.l, - rgb_color->a(), - ctx, - pstate); - } - - Signature lighten_sig = "lighten($color, $amount)"; - BUILT_IN(lighten) - { - Color_Ptr rgb_color = ARG("$color", Color); - double amount = DARG_U_PRCT("$amount"); - HSL hsl_color = rgb_to_hsl(rgb_color->r(), - rgb_color->g(), - rgb_color->b()); - //Check lightness is not negative before lighten it - double hslcolorL = hsl_color.l; - if (hslcolorL < 0) { - hslcolorL = 0; - } - - return hsla_impl(hsl_color.h, - hsl_color.s, - hslcolorL + amount, - rgb_color->a(), - ctx, - pstate); - } - - Signature darken_sig = "darken($color, $amount)"; - BUILT_IN(darken) - { - Color_Ptr rgb_color = ARG("$color", Color); - double amount = DARG_U_PRCT("$amount"); - HSL hsl_color = rgb_to_hsl(rgb_color->r(), - rgb_color->g(), - rgb_color->b()); - - //Check lightness if not over 100, before darken it - double hslcolorL = hsl_color.l; - if (hslcolorL > 100) { - hslcolorL = 100; - } - - return hsla_impl(hsl_color.h, - hsl_color.s, - hslcolorL - amount, - rgb_color->a(), - ctx, - pstate); - } - - 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) + ")"); - } - - double amount = DARG_U_PRCT("$amount"); - Color_Ptr rgb_color = ARG("$color", Color); - HSL hsl_color = rgb_to_hsl(rgb_color->r(), - rgb_color->g(), - rgb_color->b()); - - double hslcolorS = hsl_color.s + amount; - - // Saturation cannot be below 0 or above 100 - if (hslcolorS < 0) { - hslcolorS = 0; - } - if (hslcolorS > 100) { - hslcolorS = 100; - } - - return hsla_impl(hsl_color.h, - hslcolorS, - hsl_color.l, - rgb_color->a(), - ctx, - pstate); - } - - Signature desaturate_sig = "desaturate($color, $amount)"; - BUILT_IN(desaturate) - { - Color_Ptr rgb_color = ARG("$color", Color); - double amount = DARG_U_PRCT("$amount"); - HSL hsl_color = rgb_to_hsl(rgb_color->r(), - rgb_color->g(), - rgb_color->b()); - - double hslcolorS = hsl_color.s - amount; - - // Saturation cannot be below 0 or above 100 - if (hslcolorS <= 0) { - hslcolorS = 0; - } - if (hslcolorS > 100) { - hslcolorS = 100; - } - - return hsla_impl(hsl_color.h, - hslcolorS, - hsl_color.l, - rgb_color->a(), - ctx, - pstate); - } - - Signature grayscale_sig = "grayscale($color)"; - BUILT_IN(grayscale) - { - // CSS3 filter function overload: pass literal through directly - Number_Ptr amount = Cast(env["$color"]); - if (amount) { - return SASS_MEMORY_NEW(String_Quoted, pstate, "grayscale(" + amount->to_string(ctx.c_options) + ")"); - } - - Color_Ptr rgb_color = ARG("$color", Color); - HSL hsl_color = rgb_to_hsl(rgb_color->r(), - rgb_color->g(), - rgb_color->b()); - return hsla_impl(hsl_color.h, - 0.0, - hsl_color.l, - rgb_color->a(), - ctx, - pstate); - } - - Signature complement_sig = "complement($color)"; - BUILT_IN(complement) - { - Color_Ptr rgb_color = ARG("$color", Color); - HSL hsl_color = rgb_to_hsl(rgb_color->r(), - rgb_color->g(), - rgb_color->b()); - return hsla_impl(hsl_color.h - 180.0, - hsl_color.s, - hsl_color.l, - rgb_color->a(), - ctx, - pstate); - } - - Signature invert_sig = "invert($color, $weight: 100%)"; - BUILT_IN(invert) - { - // CSS3 filter function overload: pass literal through directly - Number_Ptr amount = Cast(env["$color"]); - if (amount) { - return SASS_MEMORY_NEW(String_Quoted, pstate, "invert(" + amount->to_string(ctx.c_options) + ")"); - } - - double weight = DARG_U_PRCT("$weight"); - Color_Ptr rgb_color = ARG("$color", Color); - Color_Obj inv = SASS_MEMORY_NEW(Color, - pstate, - 255 - rgb_color->r(), - 255 - rgb_color->g(), - 255 - rgb_color->b(), - rgb_color->a()); - return colormix(ctx, pstate, inv, rgb_color, weight); - } - - //////////////////// - // OPACITY FUNCTIONS - //////////////////// - Signature alpha_sig = "alpha($color)"; - Signature opacity_sig = "opacity($color)"; - BUILT_IN(alpha) - { - String_Constant_Ptr ie_kwd = Cast(env["$color"]); - if (ie_kwd) { - return SASS_MEMORY_NEW(String_Quoted, pstate, "alpha(" + ie_kwd->value() + ")"); - } - - // CSS3 filter function overload: pass literal through directly - Number_Ptr amount = Cast(env["$color"]); - if (amount) { - return SASS_MEMORY_NEW(String_Quoted, pstate, "opacity(" + amount->to_string(ctx.c_options) + ")"); - } - - return SASS_MEMORY_NEW(Number, pstate, ARG("$color", Color)->a()); - } - - Signature opacify_sig = "opacify($color, $amount)"; - Signature fade_in_sig = "fade-in($color, $amount)"; - BUILT_IN(opacify) - { - Color_Ptr color = ARG("$color", Color); - double amount = DARG_U_FACT("$amount"); - double alpha = std::min(color->a() + amount, 1.0); - return SASS_MEMORY_NEW(Color, - pstate, - color->r(), - color->g(), - color->b(), - alpha); - } - - Signature transparentize_sig = "transparentize($color, $amount)"; - Signature fade_out_sig = "fade-out($color, $amount)"; - BUILT_IN(transparentize) - { - Color_Ptr color = ARG("$color", Color); - double amount = DARG_U_FACT("$amount"); - double alpha = std::max(color->a() - amount, 0.0); - return SASS_MEMORY_NEW(Color, - pstate, - color->r(), - color->g(), - color->b(), - alpha); - } - - //////////////////////// - // OTHER COLOR FUNCTIONS - //////////////////////// - - 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_Ptr color = ARG("$color", Color); - Number_Ptr r = Cast(env["$red"]); - Number_Ptr g = Cast(env["$green"]); - Number_Ptr b = Cast(env["$blue"]); - Number_Ptr h = Cast(env["$hue"]); - Number_Ptr s = Cast(env["$saturation"]); - Number_Ptr l = Cast(env["$lightness"]); - Number_Ptr 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); - } - if (rgb) { - double rr = r ? DARG_R_BYTE("$red") : 0; - double gg = g ? DARG_R_BYTE("$green") : 0; - double bb = b ? DARG_R_BYTE("$blue") : 0; - double aa = a ? DARG_R_FACT("$alpha") : 0; - return SASS_MEMORY_NEW(Color, - pstate, - color->r() + rr, - color->g() + gg, - color->b() + bb, - color->a() + aa); - } - if (hsl) { - HSL hsl_struct = rgb_to_hsl(color->r(), color->g(), color->b()); - double ss = s ? DARG_R_PRCT("$saturation") : 0; - double ll = l ? DARG_R_PRCT("$lightness") : 0; - double aa = a ? DARG_R_FACT("$alpha") : 0; - return hsla_impl(hsl_struct.h + (h ? h->value() : 0), - hsl_struct.s + ss, - hsl_struct.l + ll, - color->a() + aa, - ctx, - pstate); - } - if (a) { - return SASS_MEMORY_NEW(Color, - pstate, - color->r(), - color->g(), - color->b(), - color->a() + (a ? a->value() : 0)); - } - error("not enough arguments for `adjust-color'", pstate, traces); - // unreachable - return color; - } - - 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) - { - Color_Ptr color = ARG("$color", Color); - Number_Ptr r = Cast(env["$red"]); - Number_Ptr g = Cast(env["$green"]); - Number_Ptr b = Cast(env["$blue"]); - Number_Ptr h = Cast(env["$hue"]); - Number_Ptr s = Cast(env["$saturation"]); - Number_Ptr l = Cast(env["$lightness"]); - Number_Ptr 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); - } - if (rgb) { - 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; - return SASS_MEMORY_NEW(Color, - pstate, - color->r() + rscale * (rscale > 0.0 ? 255 - color->r() : color->r()), - color->g() + gscale * (gscale > 0.0 ? 255 - color->g() : color->g()), - color->b() + bscale * (bscale > 0.0 ? 255 - color->b() : color->b()), - color->a() + ascale * (ascale > 0.0 ? 1.0 - color->a() : color->a())); - } - if (hsl) { - 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; - HSL hsl_struct = rgb_to_hsl(color->r(), color->g(), color->b()); - hsl_struct.h += hscale * (hscale > 0.0 ? 360.0 - hsl_struct.h : hsl_struct.h); - hsl_struct.s += sscale * (sscale > 0.0 ? 100.0 - hsl_struct.s : hsl_struct.s); - hsl_struct.l += lscale * (lscale > 0.0 ? 100.0 - hsl_struct.l : hsl_struct.l); - double alpha = color->a() + ascale * (ascale > 0.0 ? 1.0 - color->a() : color->a()); - return hsla_impl(hsl_struct.h, hsl_struct.s, hsl_struct.l, alpha, ctx, pstate); - } - if (a) { - double ascale = (DARG_R_PRCT("$alpha")) / 100.0; - return SASS_MEMORY_NEW(Color, - pstate, - color->r(), - color->g(), - color->b(), - color->a() + ascale * (ascale > 0.0 ? 1.0 - color->a() : color->a())); - } - error("not enough arguments for `scale-color'", pstate, traces); - // unreachable - return color; - } - - 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) - { - Color_Ptr color = ARG("$color", Color); - Number_Ptr r = Cast(env["$red"]); - Number_Ptr g = Cast(env["$green"]); - Number_Ptr b = Cast(env["$blue"]); - Number_Ptr h = Cast(env["$hue"]); - Number_Ptr s = Cast(env["$saturation"]); - Number_Ptr l = Cast(env["$lightness"]); - Number_Ptr 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); - } - if (rgb) { - return SASS_MEMORY_NEW(Color, - pstate, - r ? DARG_U_BYTE("$red") : color->r(), - g ? DARG_U_BYTE("$green") : color->g(), - b ? DARG_U_BYTE("$blue") : color->b(), - a ? DARG_U_BYTE("$alpha") : color->a()); - } - if (hsl) { - HSL hsl_struct = rgb_to_hsl(color->r(), color->g(), color->b()); - if (h) hsl_struct.h = std::fmod(h->value(), 360.0); - if (s) hsl_struct.s = DARG_U_PRCT("$saturation"); - if (l) hsl_struct.l = DARG_U_PRCT("$lightness"); - double alpha = a ? DARG_U_FACT("$alpha") : color->a(); - return hsla_impl(hsl_struct.h, hsl_struct.s, hsl_struct.l, alpha, ctx, pstate); - } - if (a) { - double alpha = DARG_U_FACT("$alpha"); - return SASS_MEMORY_NEW(Color, - pstate, - color->r(), - color->g(), - color->b(), - alpha); - } - error("not enough arguments for `change-color'", pstate, traces); - // unreachable - return color; - } - - template - static double cap_channel(double c) { - if (c > range) return range; - else if (c < 0) return 0; - else return c; - } - - Signature ie_hex_str_sig = "ie-hex-str($color)"; - BUILT_IN(ie_hex_str) - { - Color_Ptr c = ARG("$color", Color); - double r = cap_channel<0xff>(c->r()); - double g = cap_channel<0xff>(c->g()); - double b = cap_channel<0xff>(c->b()); - double a = cap_channel<1> (c->a()) * 255; - - std::stringstream 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)); - - std::string result(ss.str()); - for (size_t i = 0, L = result.length(); i < L; ++i) { - result[i] = std::toupper(result[i]); - } - return SASS_MEMORY_NEW(String_Quoted, pstate, result); - } - - /////////////////// - // STRING FUNCTIONS - /////////////////// - - Signature unquote_sig = "unquote($string)"; - BUILT_IN(sass_unquote) - { - AST_Node_Obj arg = env["$string"]; - if (String_Quoted_Ptr string_quoted = Cast(arg)) { - String_Constant_Ptr 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_Ptr str = Cast(arg)) { - return str; - } - else if (Expression_Ptr ex = Cast(arg)) { - Sass_Output_Style oldstyle = ctx.c_options.output_style; - ctx.c_options.output_style = SASS_STYLE_NESTED; - std::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) - { - AST_Node_Obj arg = env["$string"]; - // only set quote mark to true if already a string - if (String_Quoted_Ptr qstr = Cast(arg)) { - qstr->quote_mark('*'); - return qstr; - } - // all other nodes must be converted to a string node - std::string str(quote(arg->to_string(ctx.c_options), String_Constant::double_quote())); - String_Quoted_Ptr result = SASS_MEMORY_NEW(String_Quoted, pstate, str); - result->quote_mark('*'); - return result; - } - - - Signature str_length_sig = "str-length($string)"; - BUILT_IN(str_length) - { - size_t len = std::string::npos; - try { - String_Constant_Ptr 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) - { - std::string str; - try { - String_Constant_Ptr s = ARG("$string", String_Constant); - str = s->value(); - str = unquote(str); - String_Constant_Ptr i = ARG("$insert", String_Constant); - std::string ins = i->value(); - ins = unquote(ins); - double index = ARGVAL("$index"); - 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_Ptr 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 = std::string::npos; - try { - String_Constant_Ptr s = ARG("$string", String_Constant); - String_Constant_Ptr t = ARG("$substring", String_Constant); - std::string str = s->value(); - str = unquote(str); - std::string substr = t->value(); - substr = unquote(substr); - - size_t c_index = str.find(substr); - if(c_index == std::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) - { - std::string newstr; - try { - String_Constant_Ptr s = ARG("$string", String_Constant); - double start_at = ARGVAL("$start-at"); - double end_at = ARGVAL("$end-at"); - String_Quoted_Ptr ss = Cast(s); - - std::string str = unquote(s->value()); - - size_t size = utf8::distance(str.begin(), str.end()); - - if (!Cast(env["$end-at"])) { - end_at = -1; - } - - 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 = 0; - } - else if (start_at == 0) { ++ start_at; } - - if (start_at <= end_at) - { - std::string::iterator start = str.begin(); - utf8::advance(start, start_at - 1, str.end()); - std::string::iterator end = start; - utf8::advance(end, end_at - start_at + 1, str.end()); - newstr = std::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_Ptr s = ARG("$string", String_Constant); - std::string str = s->value(); - - for (size_t i = 0, L = str.length(); i < L; ++i) { - if (Sass::Util::isAscii(str[i])) { - str[i] = std::toupper(str[i]); - } - } - - if (String_Quoted_Ptr ss = Cast(s)) { - String_Quoted_Ptr 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_Ptr s = ARG("$string", String_Constant); - std::string str = s->value(); - - for (size_t i = 0, L = str.length(); i < L; ++i) { - if (Sass::Util::isAscii(str[i])) { - str[i] = std::tolower(str[i]); - } - } - - if (String_Quoted_Ptr ss = Cast(s)) { - String_Quoted_Ptr cpy = SASS_MEMORY_COPY(ss); - cpy->value(str); - return cpy; - } else { - return SASS_MEMORY_NEW(String_Quoted, pstate, str); - } - } - - /////////////////// - // NUMBER FUNCTIONS - /////////////////// - - Signature percentage_sig = "percentage($number)"; - BUILT_IN(percentage) - { - Number_Obj n = ARGN("$number"); - if (!n->is_unitless()) error("argument $number of `" + std::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(); - } - - 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(); - } - - Signature min_sig = "min($numbers...)"; - BUILT_IN(min) - { - List_Ptr arglist = ARG("$numbers", List); - Number_Obj least = NULL; - for (size_t i = 0, L = arglist->length(); i < L; ++i) { - Expression_Obj 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); - } - if (least) { - if (*xi < *least) least = xi; - } else least = xi; - } - return least.detach(); - } - - Signature max_sig = "max($numbers...)"; - BUILT_IN(max) - { - List_Ptr arglist = ARG("$numbers", List); - Number_Obj greatest = NULL; - for (size_t i = 0, L = arglist->length(); i < L; ++i) { - Expression_Obj 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); - } - if (greatest) { - if (*greatest < *xi) greatest = xi; - } else greatest = xi; - } - return greatest.detach(); - } - - Signature random_sig = "random($limit:false)"; - BUILT_IN(random) - { - AST_Node_Obj arg = env["$limit"]; - Value_Ptr v = Cast(arg); - Number_Ptr l = Cast(arg); - Boolean_Ptr b = Cast(arg); - if (l) { - double lv = l->value(); - if (lv < 1) { - stringstream err; - err << "$limit " << lv << " must be greater than or equal to 1 for `random'"; - error(err.str(), pstate, traces); - } - bool eq_int = std::fabs(trunc(lv) - lv) < NUMBER_EPSILON; - if (!eq_int) { - stringstream err; - err << "Expected $limit to be an integer but got " << lv << " for `random'"; - error(err.str(), pstate, traces); - } - std::uniform_real_distribution<> distributor(1, lv + 1); - uint_fast32_t distributed = static_cast(distributor(rand)); - return SASS_MEMORY_NEW(Number, pstate, (double)distributed); - } - 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"); - } - } - - ///////////////// - // LIST FUNCTIONS - ///////////////// - - Signature length_sig = "length($list)"; - BUILT_IN(length) - { - if (Selector_List_Ptr sl = Cast(env["$list"])) { - return SASS_MEMORY_NEW(Number, pstate, (double)sl->length()); - } - Expression_Ptr v = ARG("$list", Expression); - if (v->concrete_type() == Expression::MAP) { - Map_Ptr map = Cast(env["$list"]); - return SASS_MEMORY_NEW(Number, pstate, (double)(map ? map->length() : 1)); - } - if (v->concrete_type() == Expression::SELECTOR) { - if (Compound_Selector_Ptr h = Cast(v)) { - return SASS_MEMORY_NEW(Number, pstate, (double)h->length()); - } else if (Selector_List_Ptr ls = Cast(v)) { - return SASS_MEMORY_NEW(Number, pstate, (double)ls->length()); - } else { - return SASS_MEMORY_NEW(Number, pstate, 1); - } - } - - List_Ptr 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_Ptr m = Cast(env["$list"]); - if (Selector_List_Ptr sl = Cast(env["$list"])) { - size_t len = m ? m->length() : sl->length(); - bool empty = m ? m->empty() : sl->empty(); - if (empty) error("argument `$list` of `" + std::string(sig) + "` must not be empty", pstate, traces); - double index = std::floor(nr < 0 ? len + nr : nr - 1); - if (index < 0 || index > len - 1) error("index out of bounds for `" + std::string(sig) + "`", pstate, traces); - // return (*sl)[static_cast(index)]; - Listize listize; - return (*sl)[static_cast(index)]->perform(&listize); - } - List_Obj l = Cast(env["$list"]); - if (nr == 0) error("argument `$n` of `" + std::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 `" + std::string(sig) + "` must not be empty", pstate, traces); - double index = std::floor(nr < 0 ? len + nr : nr - 1); - if (index < 0 || index > len - 1) error("index out of bounds for `" + std::string(sig) + "`", pstate, traces); - - if (m) { - l = SASS_MEMORY_NEW(List, pstate, 1); - l->append(m->keys()[static_cast(index)]); - l->append(m->at(m->keys()[static_cast(index)])); - return l.detach(); - } - else { - Expression_Obj 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); - Expression_Obj 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 `" + std::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 `" + std::string(sig) + "`", pstate, traces); - List_Ptr 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"]); - Expression_Obj 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); - } - - 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(); - std::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 `" + std::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(); - } - - Signature append_sig = "append($list, $val, $separator: auto)"; - BUILT_IN(append) - { - Map_Obj m = Cast(env["$list"]); - List_Obj l = Cast(env["$list"]); - Expression_Obj v = ARG("$val", Expression); - if (Selector_List_Ptr sl = Cast(env["$list"])) { - Listize listize; - l = Cast(sl->perform(&listize)); - } - 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_Ptr result = SASS_MEMORY_COPY(l); - std::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 `" + std::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; - } - - 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)); - } - if (arglist->is_arglist()) { - Argument_Obj arg = (Argument_Ptr)(arglist->at(i).ptr()); // XXX - arg->value(ith); - } else { - (*arglist)[i] = ith; - } - } - shortest = (i ? std::min(shortest, ith->length()) : ith->length()); - } - List_Ptr zippers = SASS_MEMORY_NEW(List, pstate, shortest, SASS_COMMA); - size_t L = arglist->length(); - for (size_t i = 0; i < shortest; ++i) { - List_Ptr 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)); - } - zippers->append(zipper); - } - 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)); - } - return SASS_MEMORY_NEW(String_Quoted, - pstate, - l->separator() == SASS_COMMA ? "comma" : "space"); - } - - ///////////////// - // 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, ctx); - Expression_Obj v = ARG("$key", Expression); - try { - Expression_Obj 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&) { - 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, ctx); - Expression_Obj 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, ctx); - List_Ptr result = SASS_MEMORY_NEW(List, pstate, m->length(), SASS_COMMA); - for ( auto key : m->keys()) { - result->append(key); - } - return result; - } - - Signature map_values_sig = "map-values($map)"; - BUILT_IN(map_values) - { - Map_Obj m = ARGM("$map", Map, ctx); - List_Ptr result = SASS_MEMORY_NEW(List, pstate, m->length(), SASS_COMMA); - for ( auto key : m->keys()) { - result->append(m->at(key)); - } - return result; - } - - Signature map_merge_sig = "map-merge($map1, $map2)"; - BUILT_IN(map_merge) - { - Map_Obj m1 = ARGM("$map1", Map, ctx); - Map_Obj m2 = ARGM("$map2", Map, ctx); - - size_t len = m1->length() + m2->length(); - Map_Ptr result = SASS_MEMORY_NEW(Map, pstate, len); - // concat not implemented for maps - *result += m1; - *result += m2; - return result; - } - - Signature map_remove_sig = "map-remove($map, $keys...)"; - BUILT_IN(map_remove) - { - bool remove; - Map_Obj m = ARGM("$map", Map, ctx); - List_Obj arglist = ARG("$keys", List); - Map_Ptr 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)); - } - if (!remove) *result << std::make_pair(key, m->at(key)); - } - return result; - } - - 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) { - Expression_Obj obj = arglist->at(i); - Argument_Obj arg = (Argument_Ptr) obj.ptr(); // XXX - std::string name = std::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(); - } - - ////////////////////////// - // INTROSPECTION FUNCTIONS - ////////////////////////// - - Signature type_of_sig = "type-of($value)"; - BUILT_IN(type_of) - { - Expression_Ptr v = ARG("$value", Expression); - return SASS_MEMORY_NEW(String_Quoted, pstate, v->type()); - } - - Signature unit_sig = "unit($number)"; - BUILT_IN(unit) - { - Number_Obj arg = ARGN("$number"); - std::string str(quote(arg->unit(), '"')); - return SASS_MEMORY_NEW(String_Quoted, pstate, str); - } - - 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); - } - - Signature comparable_sig = "comparable($number-1, $number-2)"; - BUILT_IN(comparable) - { - Number_Obj n1 = ARGN("$number-1"); - Number_Obj n2 = ARGN("$number-2"); - if (n1->is_unitless() || n2->is_unitless()) { - 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); - } - - Signature variable_exists_sig = "variable-exists($name)"; - BUILT_IN(variable_exists) - { - std::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) - { - std::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_Ptr ss = Cast(env["$name"]); - if (!ss) { - error("$name: " + (env["$name"]->to_string()) + " is not a string for `function-exists'", pstate, traces); - } - - std::string name = Util::normalize_underscores(unquote(ss->value())); - - if(d_env.has_global(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) - { - std::string s = Util::normalize_underscores(unquote(ARG("$name", String_Constant)->value())); - - if(d_env.has_global(s+"[m]")) { - return SASS_MEMORY_NEW(Boolean, pstate, true); - } - else { - return SASS_MEMORY_NEW(Boolean, pstate, false); - } - } - - Signature feature_exists_sig = "feature-exists($name)"; - BUILT_IN(feature_exists) - { - std::string s = unquote(ARG("$name", String_Constant)->value()); - - if(features.find(s) == features.end()) { - return SASS_MEMORY_NEW(Boolean, pstate, false); - } - else { - return SASS_MEMORY_NEW(Boolean, pstate, true); - } - } - - Signature call_sig = "call($name, $args...)"; - BUILT_IN(call) - { - std::string name; - Function_Ptr ff = Cast(env["$name"]); - String_Constant_Ptr ss = Cast(env["$name"]); - - if (ss) { - name = 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(name) + ")) instead." << std::endl; - std::cerr << std::endl; - } else if (ff) { - name = ff->name(); - } - - List_Obj arglist = SASS_MEMORY_COPY(ARG("$args", List)); - - Arguments_Obj args = SASS_MEMORY_NEW(Arguments, pstate); - // std::string full_name(name + "[f]"); - // Definition_Ptr def = d_env.has(full_name) ? Cast((d_env)[full_name]) : 0; - // Parameters_Ptr params = def ? def->parameters() : 0; - // size_t param_size = params ? params->length() : 0; - for (size_t i = 0, L = arglist->length(); i < L; ++i) { - Expression_Obj expr = arglist->value_at_index(i); - // if (params && params->has_rest_parameter()) { - // Parameter_Obj p = param_size > i ? (*params)[i] : 0; - // List_Ptr list = Cast(expr); - // if (list && p && !p->is_rest_parameter()) expr = (*list)[0]; - // } - if (arglist->is_arglist()) { - Expression_Obj obj = arglist->at(i); - Argument_Obj arg = (Argument_Ptr) 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, name, args); - Expand expand(ctx, &d_env, &selector_stack); - func->via_call(true); // calc invoke is allowed - if (ff) func->func(ff); - return 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) - // { return ARG("$condition", Expression)->is_false() ? ARG("$if-false", Expression) : ARG("$if-true", Expression); } - BUILT_IN(sass_if) - { - Expand expand(ctx, &d_env, &selector_stack); - Expression_Obj cond = ARG("$condition", Expression)->perform(&expand.eval); - bool is_true = !cond->is_false(); - Expression_Obj res = ARG(is_true ? "$if-true" : "$if-false", Expression); - res = res->perform(&expand.eval); - res->set_delayed(false); // clone? - return res.detach(); - } - - ////////////////////////// - // MISCELLANEOUS FUNCTIONS - ////////////////////////// - - // value.check_deprecated_interp if value.is_a?(Sass::Script::Value::String) - // unquoted_string(value.to_sass) - - Signature inspect_sig = "inspect($value)"; - BUILT_IN(inspect) - { - Expression_Ptr v = ARG("$value", Expression); - if (v->concrete_type() == Expression::NULL_VAL) { - return SASS_MEMORY_NEW(String_Quoted, pstate, "null"); - } else if (v->concrete_type() == Expression::BOOLEAN && v->is_false()) { - return SASS_MEMORY_NEW(String_Quoted, pstate, "false"); - } else if (v->concrete_type() == Expression::STRING) { - return v; - } 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()); - } - // return v; - } - Signature selector_nest_sig = "selector-nest($selectors...)"; - BUILT_IN(selector_nest) - { - List_Ptr 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); - - // Parse args into vector of selectors - std::vector parsedSelectors; - for (size_t i = 0, L = arglist->length(); i < L; ++i) { - Expression_Obj exp = Cast(arglist->value_at_index(i)); - if (exp->concrete_type() == Expression::NULL_VAL) { - std::stringstream msg; - msg << "$selectors: null is not a valid selector: it must be a string,\n"; - msg << "a list of strings, or a list of lists of strings for 'selector-nest'"; - error(msg.str(), pstate, traces); - } - if (String_Constant_Obj str = Cast(exp)) { - str->quote_mark(0); - } - std::string exp_src = exp->to_string(ctx.c_options); - Selector_List_Obj sel = Parser::parse_selector(exp_src.c_str(), ctx, traces); - parsedSelectors.push_back(sel); - } - - // Nothing to do - if( parsedSelectors.empty() ) { - return SASS_MEMORY_NEW(Null, pstate); - } - - // Set the first element as the `result`, keep appending to as we go down the parsedSelector vector. - std::vector::iterator itr = parsedSelectors.begin(); - Selector_List_Obj result = *itr; - ++itr; - - for(;itr != parsedSelectors.end(); ++itr) { - Selector_List_Obj child = *itr; - std::vector exploded; - selector_stack.push_back(result); - Selector_List_Obj rv = child->resolve_parent_refs(selector_stack, traces); - selector_stack.pop_back(); - for (size_t m = 0, mLen = rv->length(); m < mLen; ++m) { - exploded.push_back((*rv)[m]); - } - result->elements(exploded); - } - - Listize listize; - return result->perform(&listize); - } - - Signature selector_append_sig = "selector-append($selectors...)"; - BUILT_IN(selector_append) - { - List_Ptr arglist = ARG("$selectors", List); - - // Not enough parameters - if( arglist->length() == 0 ) - error("$selectors: At least one selector must be passed for `selector-append'", pstate, traces); - - // Parse args into vector of selectors - std::vector parsedSelectors; - for (size_t i = 0, L = arglist->length(); i < L; ++i) { - Expression_Obj exp = Cast(arglist->value_at_index(i)); - if (exp->concrete_type() == Expression::NULL_VAL) { - std::stringstream msg; - msg << "$selectors: null is not a valid selector: it must be a string,\n"; - msg << "a list of strings, or a list of lists of strings for 'selector-append'"; - error(msg.str(), pstate, traces); - } - if (String_Constant_Ptr str = Cast(exp)) { - str->quote_mark(0); - } - std::string exp_src = exp->to_string(); - Selector_List_Obj sel = Parser::parse_selector(exp_src.c_str(), ctx, traces); - parsedSelectors.push_back(sel); - } - - // Nothing to do - if( parsedSelectors.empty() ) { - return SASS_MEMORY_NEW(Null, pstate); - } - - // Set the first element as the `result`, keep appending to as we go down the parsedSelector vector. - std::vector::iterator itr = parsedSelectors.begin(); - Selector_List_Obj result = *itr; - ++itr; - - for(;itr != parsedSelectors.end(); ++itr) { - Selector_List_Obj child = *itr; - std::vector newElements; - - // For every COMPLEX_SELECTOR in `result` - // For every COMPLEX_SELECTOR in `child` - // let parentSeqClone equal a copy of result->elements[i] - // let childSeq equal child->elements[j] - // Append all of childSeq head elements into parentSeqClone - // Set the innermost tail of parentSeqClone, to childSeq's tail - // Replace result->elements with newElements - for (size_t i = 0, resultLen = result->length(); i < resultLen; ++i) { - for (size_t j = 0, childLen = child->length(); j < childLen; ++j) { - Complex_Selector_Obj parentSeqClone = SASS_MEMORY_CLONE((*result)[i]); - Complex_Selector_Obj childSeq = (*child)[j]; - Complex_Selector_Obj base = childSeq->tail(); - - // Must be a simple sequence - if( childSeq->combinator() != Complex_Selector::Combinator::ANCESTOR_OF ) { - std::string msg("Can't append \""); - msg += childSeq->to_string(); - msg += "\" to \""; - msg += parentSeqClone->to_string(); - msg += "\" for `selector-append'"; - error(msg, pstate, traces); - } - - // Cannot be a Universal selector - Element_Selector_Obj pType = Cast(childSeq->head()->first()); - if(pType && pType->name() == "*") { - std::string msg("Can't append \""); - msg += childSeq->to_string(); - msg += "\" to \""; - msg += parentSeqClone->to_string(); - msg += "\" for `selector-append'"; - error(msg, pstate, traces); - } - - // TODO: Add check for namespace stuff - - // append any selectors in childSeq's head - parentSeqClone->innermost()->head()->concat(base->head()); - - // Set parentSeqClone new tail - parentSeqClone->innermost()->tail( base->tail() ); - - newElements.push_back(parentSeqClone); - } - } - - result->elements(newElements); - } - - Listize listize; - return result->perform(&listize); - } - - Signature selector_unify_sig = "selector-unify($selector1, $selector2)"; - BUILT_IN(selector_unify) - { - Selector_List_Obj selector1 = ARGSEL("$selector1", Selector_List_Obj, p_contextualize); - Selector_List_Obj selector2 = ARGSEL("$selector2", Selector_List_Obj, p_contextualize); - - Selector_List_Obj result = selector1->unify_with(selector2); - Listize listize; - return result->perform(&listize); - } - - Signature simple_selectors_sig = "simple-selectors($selector)"; - BUILT_IN(simple_selectors) - { - Compound_Selector_Obj sel = ARGSEL("$selector", Compound_Selector_Obj, p_contextualize); - - List_Ptr l = SASS_MEMORY_NEW(List, sel->pstate(), sel->length(), SASS_COMMA); - - for (size_t i = 0, L = sel->length(); i < L; ++i) { - Simple_Selector_Obj ss = (*sel)[i]; - std::string ss_string = ss->to_string() ; - - l->append(SASS_MEMORY_NEW(String_Quoted, ss->pstate(), ss_string)); - } - - return l; - } - - Signature selector_extend_sig = "selector-extend($selector, $extendee, $extender)"; - BUILT_IN(selector_extend) - { - Selector_List_Obj selector = ARGSEL("$selector", Selector_List_Obj, p_contextualize); - Selector_List_Obj extendee = ARGSEL("$extendee", Selector_List_Obj, p_contextualize); - Selector_List_Obj extender = ARGSEL("$extender", Selector_List_Obj, p_contextualize); - - Subset_Map subset_map; - extender->populate_extends(extendee, subset_map); - Extend extend(subset_map); - - Selector_List_Obj result = extend.extendSelectorList(selector, false); - - Listize listize; - return result->perform(&listize); - } - - Signature selector_replace_sig = "selector-replace($selector, $original, $replacement)"; - BUILT_IN(selector_replace) - { - Selector_List_Obj selector = ARGSEL("$selector", Selector_List_Obj, p_contextualize); - Selector_List_Obj original = ARGSEL("$original", Selector_List_Obj, p_contextualize); - Selector_List_Obj replacement = ARGSEL("$replacement", Selector_List_Obj, p_contextualize); - Subset_Map subset_map; - replacement->populate_extends(original, subset_map); - Extend extend(subset_map); - - Selector_List_Obj result = extend.extendSelectorList(selector, true); - - Listize listize; - return result->perform(&listize); - } - - Signature selector_parse_sig = "selector-parse($selector)"; - BUILT_IN(selector_parse) - { - Selector_List_Obj sel = ARGSEL("$selector", Selector_List_Obj, p_contextualize); - - Listize listize; - return sel->perform(&listize); - } - - Signature is_superselector_sig = "is-superselector($super, $sub)"; - BUILT_IN(is_superselector) - { - Selector_List_Obj sel_sup = ARGSEL("$super", Selector_List_Obj, p_contextualize); - Selector_List_Obj sel_sub = ARGSEL("$sub", Selector_List_Obj, p_contextualize); - bool result = sel_sup->is_superselector_of(sel_sub); - return SASS_MEMORY_NEW(Boolean, pstate, result); - } - - Signature unique_id_sig = "unique-id()"; - BUILT_IN(unique_id) - { - std::stringstream 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 is_bracketed_sig = "is-bracketed($list)"; - BUILT_IN(is_bracketed) - { - Value_Obj value = ARG("$list", Value); - List_Obj list = Cast(value); - return SASS_MEMORY_NEW(Boolean, pstate, list && list->is_bracketed()); - } - - 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_Ptr ss = Cast(env["$name"]); - if (!ss) { - error("$name: " + (env["$name"]->to_string()) + " is not a string for `get-function'", pstate, traces); - } - - std::string name = Util::normalize_underscores(unquote(ss->value())); - std::string full_name = name + "[f]"; - - Boolean_Obj css = ARG("$css", Boolean); - if (!css->is_false()) { - Definition_Ptr 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_Ptr def = Cast(d_env[full_name]); - return SASS_MEMORY_NEW(Function, pstate, def, false); - } - } -} diff --git a/src/libsass/src/functions.hpp b/src/libsass/src/functions.hpp deleted file mode 100644 index 7019be934..000000000 --- a/src/libsass/src/functions.hpp +++ /dev/null @@ -1,198 +0,0 @@ -#ifndef SASS_FUNCTIONS_H -#define SASS_FUNCTIONS_H - -#include "listize.hpp" -#include "position.hpp" -#include "environment.hpp" -#include "ast_fwd_decl.hpp" -#include "sass/functions.h" - -#define BUILT_IN(name) Expression_Ptr \ -name(Env& env, Env& d_env, Context& ctx, Signature sig, ParserState pstate, Backtraces traces, std::vector selector_stack) - -namespace Sass { - struct Backtrace; - typedef const char* Signature; - typedef Expression_Ptr (*Native_Function)(Env&, Env&, Context&, Signature, ParserState, Backtraces, std::vector); - - Definition_Ptr make_native_function(Signature, Native_Function, Context& ctx); - Definition_Ptr make_c_function(Sass_Function_Entry c_func, Context& ctx); - - std::string function_name(Signature); - - namespace Functions { - - 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; - 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 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 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 type_of_sig; - extern Signature unit_sig; - extern Signature unitless_sig; - extern Signature comparable_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 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; - extern Signature keywords_sig; - extern Signature set_nth_sig; - extern Signature unique_id_sig; - 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; - extern Signature is_bracketed_sig; - extern Signature content_exists_sig; - extern Signature get_function_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); - 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(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(length); - BUILT_IN(nth); - BUILT_IN(index); - BUILT_IN(join); - BUILT_IN(append); - BUILT_IN(zip); - BUILT_IN(list_separator); - BUILT_IN(type_of); - BUILT_IN(unit); - BUILT_IN(unitless); - BUILT_IN(comparable); - 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(map_get); - BUILT_IN(map_merge); - BUILT_IN(map_remove); - BUILT_IN(map_keys); - BUILT_IN(map_values); - BUILT_IN(map_has_key); - BUILT_IN(keywords); - BUILT_IN(set_nth); - BUILT_IN(unique_id); - 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); - BUILT_IN(is_bracketed); - BUILT_IN(content_exists); - BUILT_IN(get_function); - } -} - -#endif diff --git a/src/libsass/src/inspect.cpp b/src/libsass/src/inspect.cpp index 5cd8cc0c7..e581edc42 100644 --- a/src/libsass/src/inspect.cpp +++ b/src/libsass/src/inspect.cpp @@ -1,4 +1,7 @@ +// sass.hpp must go before all system headers to get the +// __EXTENSIONS__ fix on Solaris. #include "sass.hpp" + #include #include #include @@ -21,7 +24,7 @@ namespace Sass { Inspect::~Inspect() { } // statements - void Inspect::operator()(Block_Ptr block) + void Inspect::operator()(Block* block) { if (!block->is_root()) { add_open_mapping(block); @@ -39,25 +42,23 @@ namespace Sass { } - void Inspect::operator()(Ruleset_Ptr ruleset) + void Inspect::operator()(Ruleset* ruleset) { if (ruleset->selector()) { - opt.in_selector = true; ruleset->selector()->perform(this); - opt.in_selector = false; } if (ruleset->block()) { ruleset->block()->perform(this); } } - void Inspect::operator()(Keyframe_Rule_Ptr rule) + void Inspect::operator()(Keyframe_Rule* rule) { if (rule->name()) rule->name()->perform(this); if (rule->block()) rule->block()->perform(this); } - void Inspect::operator()(Bubble_Ptr bubble) + void Inspect::operator()(Bubble* bubble) { append_indentation(); append_token("::BUBBLE", bubble); @@ -66,18 +67,64 @@ namespace Sass { append_scope_closer(); } - void Inspect::operator()(Media_Block_Ptr media_block) + void Inspect::operator()(MediaRule* rule) + { + append_indentation(); + append_token("@media", rule); + append_mandatory_space(); + if (rule->block()) { + rule->block()->perform(this); + } + } + + void Inspect::operator()(CssMediaRule* rule) { + if (output_style() == NESTED) + indentation += rule->tabs(); append_indentation(); - append_token("@media", media_block); + append_token("@media", rule); append_mandatory_space(); in_media_block = true; - media_block->media_queries()->perform(this); + bool joinIt = false; + for (auto query : rule->elements()) { + if (joinIt) { + append_comma_separator(); + append_optional_space(); + } + operator()(query); + joinIt = true; + } + if (rule->block()) { + rule->block()->perform(this); + } in_media_block = false; - media_block->block()->perform(this); + if (output_style() == NESTED) + indentation -= rule->tabs(); + } + + void Inspect::operator()(CssMediaQuery* query) + { + bool joinIt = false; + if (!query->modifier().empty()) { + append_string(query->modifier()); + append_mandatory_space(); + } + if (!query->type().empty()) { + append_string(query->type()); + joinIt = true; + } + for (auto feature : query->features()) { + if (joinIt) { + append_mandatory_space(); + append_string("and"); + append_mandatory_space(); + } + append_string(feature); + joinIt = true; + } } - void Inspect::operator()(Supports_Block_Ptr feature_block) + void Inspect::operator()(Supports_Block* feature_block) { append_indentation(); append_token("@supports", feature_block); @@ -86,7 +133,7 @@ namespace Sass { feature_block->block()->perform(this); } - void Inspect::operator()(At_Root_Block_Ptr at_root_block) + void Inspect::operator()(At_Root_Block* at_root_block) { append_indentation(); append_token("@at-root ", at_root_block); @@ -95,7 +142,7 @@ namespace Sass { if(at_root_block->block()) at_root_block->block()->perform(this); } - void Inspect::operator()(Directive_Ptr at_rule) + void Inspect::operator()(Directive* at_rule) { append_indentation(); append_token(at_rule->keyword(), at_rule); @@ -118,7 +165,7 @@ namespace Sass { } } - void Inspect::operator()(Declaration_Ptr dec) + void Inspect::operator()(Declaration* dec) { if (dec->value()->concrete_type() == Expression::NULL_VAL) return; bool was_decl = in_declaration; @@ -133,8 +180,7 @@ namespace Sass { append_colon_separator(); if (dec->value()->concrete_type() == Expression::SELECTOR) { - Listize listize; - Expression_Obj ls = dec->value()->perform(&listize); + Expression_Obj ls = Listize::perform(dec->value()); ls->perform(this); } else { dec->value()->perform(this); @@ -150,7 +196,7 @@ namespace Sass { in_declaration = was_decl; } - void Inspect::operator()(Assignment_Ptr assn) + void Inspect::operator()(Assignment* assn) { append_token(assn->variable(), assn); append_colon_separator(); @@ -162,7 +208,7 @@ namespace Sass { append_delimiter(); } - void Inspect::operator()(Import_Ptr import) + void Inspect::operator()(Import* import) { if (!import->urls().empty()) { append_token("@import", import); @@ -193,7 +239,7 @@ namespace Sass { } } - void Inspect::operator()(Import_Stub_Ptr import) + void Inspect::operator()(Import_Stub* import) { append_indentation(); append_token("@import", import); @@ -202,7 +248,7 @@ namespace Sass { append_delimiter(); } - void Inspect::operator()(Warning_Ptr warning) + void Inspect::operator()(Warning* warning) { append_indentation(); append_token("@warn", warning); @@ -211,7 +257,7 @@ namespace Sass { append_delimiter(); } - void Inspect::operator()(Error_Ptr error) + void Inspect::operator()(Error* error) { append_indentation(); append_token("@error", error); @@ -220,7 +266,7 @@ namespace Sass { append_delimiter(); } - void Inspect::operator()(Debug_Ptr debug) + void Inspect::operator()(Debug* debug) { append_indentation(); append_token("@debug", debug); @@ -229,14 +275,14 @@ namespace Sass { append_delimiter(); } - void Inspect::operator()(Comment_Ptr comment) + void Inspect::operator()(Comment* comment) { in_comment = true; comment->text()->perform(this); in_comment = false; } - void Inspect::operator()(If_Ptr cond) + void Inspect::operator()(If* cond) { append_indentation(); append_token("@if", cond); @@ -251,7 +297,7 @@ namespace Sass { } } - void Inspect::operator()(For_Ptr loop) + void Inspect::operator()(For* loop) { append_indentation(); append_token("@for", loop); @@ -264,7 +310,7 @@ namespace Sass { loop->block()->perform(this); } - void Inspect::operator()(Each_Ptr loop) + void Inspect::operator()(Each* loop) { append_indentation(); append_token("@each", loop); @@ -279,7 +325,7 @@ namespace Sass { loop->block()->perform(this); } - void Inspect::operator()(While_Ptr loop) + void Inspect::operator()(While* loop) { append_indentation(); append_token("@while", loop); @@ -288,7 +334,7 @@ namespace Sass { loop->block()->perform(this); } - void Inspect::operator()(Return_Ptr ret) + void Inspect::operator()(Return* ret) { append_indentation(); append_token("@return", ret); @@ -297,7 +343,7 @@ namespace Sass { append_delimiter(); } - void Inspect::operator()(Extension_Ptr extend) + void Inspect::operator()(ExtendRule* extend) { append_indentation(); append_token("@extend", extend); @@ -306,7 +352,7 @@ namespace Sass { append_delimiter(); } - void Inspect::operator()(Definition_Ptr def) + void Inspect::operator()(Definition* def) { append_indentation(); if (def->type() == Definition::MIXIN) { @@ -321,7 +367,7 @@ namespace Sass { def->block()->perform(this); } - void Inspect::operator()(Mixin_Call_Ptr call) + void Inspect::operator()(Mixin_Call* call) { append_indentation(); append_token("@include", call); @@ -337,14 +383,14 @@ namespace Sass { if (!call->block()) append_delimiter(); } - void Inspect::operator()(Content_Ptr content) + void Inspect::operator()(Content* content) { append_indentation(); append_token("@content", content); append_delimiter(); } - void Inspect::operator()(Map_Ptr map) + void Inspect::operator()(Map* map) { if (output_style() == TO_SASS && map->empty()) { append_string("()"); @@ -366,15 +412,15 @@ namespace Sass { append_string(")"); } - std::string Inspect::lbracket(List_Ptr list) { + std::string Inspect::lbracket(List* list) { return list->is_bracketed() ? "[" : "("; } - std::string Inspect::rbracket(List_Ptr list) { + std::string Inspect::rbracket(List* list) { return list->is_bracketed() ? "]" : ")"; } - void Inspect::operator()(List_Ptr list) + void Inspect::operator()(List* list) { if (list->empty() && (output_style() == TO_SASS || list->is_bracketed())) { append_string(lbracket(list)); @@ -398,7 +444,7 @@ namespace Sass { list->length() == 1 && !list->from_selector() && !Cast(list->at(0)) && - !Cast(list->at(0)) + !Cast(list->at(0)) ) { append_string(lbracket(list)); } @@ -448,7 +494,7 @@ namespace Sass { list->length() == 1 && !list->from_selector() && !Cast(list->at(0)) && - !Cast(list->at(0)) + !Cast(list->at(0)) ) { append_string(","); append_string(rbracket(list)); @@ -462,7 +508,7 @@ namespace Sass { } - void Inspect::operator()(Binary_Expression_Ptr expr) + void Inspect::operator()(Binary_Expression* expr) { expr->left()->perform(this); if ( in_media_block || @@ -499,7 +545,7 @@ namespace Sass { expr->right()->perform(this); } - void Inspect::operator()(Unary_Expression_Ptr expr) + void Inspect::operator()(Unary_Expression* expr) { if (expr->optype() == Unary_Expression::PLUS) append_string("+"); else if (expr->optype() == Unary_Expression::SLASH) append_string("/"); @@ -507,91 +553,42 @@ namespace Sass { expr->operand()->perform(this); } - void Inspect::operator()(Function_Call_Ptr call) + void Inspect::operator()(Function_Call* call) { append_token(call->name(), call); call->arguments()->perform(this); } - void Inspect::operator()(Function_Call_Schema_Ptr call) - { - call->name()->perform(this); - call->arguments()->perform(this); - } - - void Inspect::operator()(Variable_Ptr var) + void Inspect::operator()(Variable* var) { append_token(var->name(), var); } - void Inspect::operator()(Number_Ptr n) + void Inspect::operator()(Number* n) { - std::string res; - // reduce units n->reduce(); - // check if the fractional part of the value equals to zero - // neat trick from http://stackoverflow.com/a/1521682/1550314 - // double int_part; bool is_int = modf(value, &int_part) == 0.0; - - // this all cannot be done with one run only, since fixed - // output differs from normal output and regular output - // can contain scientific notation which we do not want! - - // first sample std::stringstream ss; - ss.precision(12); - ss << n->value(); - - // check if we got scientific notation in result - if (ss.str().find_first_of("e") != std::string::npos) { - ss.clear(); ss.str(std::string()); - ss.precision(std::max(12, opt.precision)); - ss << std::fixed << n->value(); - } - - std::string tmp = ss.str(); - size_t pos_point = tmp.find_first_of(".,"); - size_t pos_fract = tmp.find_last_not_of("0"); - bool is_int = pos_point == pos_fract || - pos_point == std::string::npos; + ss.precision(opt.precision); + ss << std::fixed << n->value(); - // reset stream for another run - ss.clear(); ss.str(std::string()); + std::string res = ss.str(); + size_t s = res.length(); - // take a shortcut for integers - if (is_int) + // delete trailing zeros + for(s = s - 1; s > 0; --s) { - ss.precision(0); - ss << std::fixed << n->value(); - res = std::string(ss.str()); - } - // process floats - else - { - // do we have have too much precision? - if (pos_fract < opt.precision + pos_point) - { ss.precision((int)(pos_fract - pos_point)); } - else { ss.precision(opt.precision); } - // round value again - ss << std::fixed << n->value(); - res = std::string(ss.str()); - // maybe we truncated up to decimal point - size_t pos = res.find_last_not_of("0"); - // handle case where we have a "0" - if (pos == std::string::npos) { - res = "0.0"; - } else { - bool at_dec_point = res[pos] == '.' || - res[pos] == ','; - // don't leave a blank point - if (at_dec_point) ++ pos; - res.resize (pos + 1); - } + if(res[s] == '0') { + res.erase(s, 1); + } + else break; } + // delete trailing decimal separator + if(res[s] == '.') res.erase(s, 1); + // some final cosmetics if (res == "0.0") res = "0"; else if (res == "") res = "0"; @@ -599,15 +596,22 @@ namespace Sass { else if (res == "-0.0") res = "0"; else if (opt.output_style == COMPRESSED) { - // check if handling negative nr - size_t off = res[0] == '-' ? 1 : 0; - // remove leading zero from floating point in compressed mode - if (n->zero() && res[off] == '0' && res[off+1] == '.') res.erase(off, 1); + 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); + } } // add unit now res += n->unit(); + if (opt.output_style == TO_CSS && !n->is_valid_css_unit()) { + // traces.push_back(Backtrace(nr->pstate())); + throw Exception::InvalidValue({}, *n); + } + // output the final token append_token(res, n); } @@ -620,7 +624,7 @@ namespace Sass { else return c; } - void Inspect::operator()(Color_Ptr c) + void Inspect::operator()(Color_RGBA* c) { // output the final token std::stringstream ss; @@ -629,11 +633,6 @@ namespace Sass { // maybe an unknown token std::string name = c->disp(); - if (opt.in_selector && name != "") { - append_token(name, c); - return; - } - // resolved color std::string res_name = name; @@ -644,7 +643,7 @@ namespace Sass { // get color from given name (if one was given at all) if (name != "" && name_to_color(name)) { - Color_Ptr_Const n = 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); @@ -711,13 +710,19 @@ namespace Sass { } - void Inspect::operator()(Boolean_Ptr b) + void Inspect::operator()(Color_HSLA* c) + { + Color_RGBA_Obj rgba = c->toRGBA(); + operator()(rgba); + } + + void Inspect::operator()(Boolean* b) { // output the final token append_token(b->value() ? "true" : "false", b); } - void Inspect::operator()(String_Schema_Ptr ss) + void Inspect::operator()(String_Schema* ss) { // Evaluation should turn these into String_Constants, // so this method is only for inspection purposes. @@ -728,12 +733,12 @@ namespace Sass { } } - void Inspect::operator()(String_Constant_Ptr s) + void Inspect::operator()(String_Constant* s) { append_token(s->value(), s); } - void Inspect::operator()(String_Quoted_Ptr s) + void Inspect::operator()(String_Quoted* s) { if (const char q = s->quote_mark()) { append_token(quote(s->value(), q), s); @@ -742,17 +747,17 @@ namespace Sass { } } - void Inspect::operator()(Custom_Error_Ptr e) + void Inspect::operator()(Custom_Error* e) { append_token(e->message(), e); } - void Inspect::operator()(Custom_Warning_Ptr w) + void Inspect::operator()(Custom_Warning* w) { append_token(w->message(), w); } - void Inspect::operator()(Supports_Operator_Ptr so) + void Inspect::operator()(Supports_Operator* so) { if (so->needs_parens(so->left())) append_string("("); @@ -774,7 +779,7 @@ namespace Sass { if (so->needs_parens(so->right())) append_string(")"); } - void Inspect::operator()(Supports_Negation_Ptr sn) + void Inspect::operator()(Supports_Negation* sn) { append_token("not", sn); append_mandatory_space(); @@ -783,7 +788,7 @@ namespace Sass { if (sn->needs_parens(sn->condition())) append_string(")"); } - void Inspect::operator()(Supports_Declaration_Ptr sd) + void Inspect::operator()(Supports_Declaration* sd) { append_string("("); sd->feature()->perform(this); @@ -792,12 +797,12 @@ namespace Sass { append_string(")"); } - void Inspect::operator()(Supports_Interpolation_Ptr sd) + void Inspect::operator()(Supports_Interpolation* sd) { sd->value()->perform(this); } - void Inspect::operator()(Media_Query_Ptr mq) + void Inspect::operator()(Media_Query* mq) { size_t i = 0; if (mq->media_type()) { @@ -814,7 +819,7 @@ namespace Sass { } } - void Inspect::operator()(Media_Query_Expression_Ptr mqe) + void Inspect::operator()(Media_Query_Expression* mqe) { if (mqe->is_interpolated()) { mqe->feature()->perform(this); @@ -830,7 +835,7 @@ namespace Sass { } } - void Inspect::operator()(At_Root_Query_Ptr ae) + void Inspect::operator()(At_Root_Query* ae) { if (ae->feature()) { append_string("("); @@ -843,7 +848,7 @@ namespace Sass { } } - void Inspect::operator()(Function_Ptr f) + void Inspect::operator()(Function* f) { append_token("get-function", f); append_string("("); @@ -851,14 +856,14 @@ namespace Sass { append_string(")"); } - void Inspect::operator()(Null_Ptr n) + void Inspect::operator()(Null* n) { // output the final token append_token("null", n); } // parameters and arguments - void Inspect::operator()(Parameter_Ptr p) + void Inspect::operator()(Parameter* p) { append_token(p->name(), p); if (p->default_value()) { @@ -870,7 +875,7 @@ namespace Sass { } } - void Inspect::operator()(Parameters_Ptr p) + void Inspect::operator()(Parameters* p) { append_string("("); if (!p->empty()) { @@ -883,7 +888,7 @@ namespace Sass { append_string(")"); } - void Inspect::operator()(Argument_Ptr a) + void Inspect::operator()(Argument* a) { if (!a->name().empty()) { append_token(a->name(), a); @@ -895,7 +900,7 @@ namespace Sass { return; } if (a->value()->concrete_type() == Expression::STRING) { - String_Constant_Ptr s = Cast(a->value()); + String_Constant* s = Cast(a->value()); if (s) s->perform(this); } else { a->value()->perform(this); @@ -905,7 +910,7 @@ namespace Sass { } } - void Inspect::operator()(Arguments_Ptr a) + void Inspect::operator()(Arguments* a) { append_string("("); if (!a->empty()) { @@ -919,46 +924,38 @@ namespace Sass { append_string(")"); } - void Inspect::operator()(Selector_Schema_Ptr s) + void Inspect::operator()(Selector_Schema* s) { - opt.in_selector = true; s->contents()->perform(this); - opt.in_selector = false; } - void Inspect::operator()(Parent_Selector_Ptr p) + void Inspect::operator()(Parent_Reference* p) { - if (p->is_real_parent_ref()) append_string("&"); + append_string("&"); } - void Inspect::operator()(Placeholder_Selector_Ptr s) + void Inspect::operator()(Placeholder_Selector* s) { append_token(s->name(), s); - if (s->has_line_break()) append_optional_linefeed(); - if (s->has_line_break()) append_indentation(); } - void Inspect::operator()(Element_Selector_Ptr s) + void Inspect::operator()(Type_Selector* s) { append_token(s->ns_name(), s); } - void Inspect::operator()(Class_Selector_Ptr s) + void Inspect::operator()(Class_Selector* s) { append_token(s->ns_name(), s); - if (s->has_line_break()) append_optional_linefeed(); - if (s->has_line_break()) append_indentation(); } - void Inspect::operator()(Id_Selector_Ptr s) + void Inspect::operator()(Id_Selector* s) { append_token(s->ns_name(), s); - if (s->has_line_break()) append_optional_linefeed(); - if (s->has_line_break()) append_indentation(); } - void Inspect::operator()(Attribute_Selector_Ptr s) + void Inspect::operator()(Attribute_Selector* s) { append_string("["); add_open_mapping(s); @@ -977,111 +974,38 @@ namespace Sass { append_string("]"); } - void Inspect::operator()(Pseudo_Selector_Ptr s) - { - append_token(s->ns_name(), s); - if (s->expression()) { - append_string("("); - s->expression()->perform(this); - append_string(")"); - } - } - - void Inspect::operator()(Wrapped_Selector_Ptr s) - { - if (s->name() == " ") { - append_string(""); - } else { - bool was = in_wrapped; - in_wrapped = true; - append_token(s->name(), s); - append_string("("); - bool was_comma_array = in_comma_array; - in_comma_array = false; - s->selector()->perform(this); - in_comma_array = was_comma_array; - append_string(")"); - in_wrapped = was; - } - } - - void Inspect::operator()(Compound_Selector_Ptr s) + void Inspect::operator()(Pseudo_Selector* s) { - for (size_t i = 0, L = s->length(); i < L; ++i) { - (*s)[i]->perform(this); - } - if (s->has_line_break()) { - if (output_style() != COMPACT) { - append_optional_linefeed(); - } - } - } - - void Inspect::operator()(Complex_Selector_Ptr c) - { - Compound_Selector_Obj head = c->head(); - Complex_Selector_Obj tail = c->tail(); - Complex_Selector::Combinator comb = c->combinator(); - - if (comb == Complex_Selector::ANCESTOR_OF && (!head || head->empty())) { - if (tail) tail->perform(this); - return; - } - if (c->has_line_feed()) { - if (!(c->has_parent_ref())) { - append_optional_linefeed(); - append_indentation(); + if (s->name() != "") { + append_string(":"); + if (s->isSyntacticElement()) { + append_string(":"); } - } - - if (head && head->length() != 0) head->perform(this); - bool is_empty = !head || head->length() == 0 || head->is_empty_reference(); - bool is_tail = head && !head->is_empty_reference() && tail; - if (output_style() == COMPRESSED && comb != Complex_Selector::ANCESTOR_OF) scheduled_space = 0; - - switch (comb) { - case Complex_Selector::ANCESTOR_OF: - if (is_tail) append_mandatory_space(); - break; - case Complex_Selector::PARENT_OF: - append_optional_space(); - append_string(">"); - append_optional_space(); - break; - case Complex_Selector::ADJACENT_TO: - append_optional_space(); - append_string("+"); - append_optional_space(); - break; - case Complex_Selector::REFERENCE: - append_mandatory_space(); - append_string("/"); - if (c->reference()) c->reference()->perform(this); - append_string("/"); - append_mandatory_space(); - break; - case Complex_Selector::PRECEDES: - if (is_empty) append_optional_space(); - else append_mandatory_space(); - append_string("~"); - if (tail) append_mandatory_space(); - else append_optional_space(); - break; - default: break; - } - if (tail && comb != Complex_Selector::ANCESTOR_OF) { - if (c->has_line_break()) append_optional_linefeed(); - } - if (tail) tail->perform(this); - if (!tail && c->has_line_break()) { - if (output_style() == COMPACT) { - append_mandatory_space(); + append_token(s->ns_name(), s); + if (s->selector() || s->argument()) { + bool was = in_wrapped; + in_wrapped = true; + append_string("("); + if (s->argument()) { + s->argument()->perform(this); + } + if (s->selector() && s->argument()) { + append_mandatory_space(); + } + bool was_comma_array = in_comma_array; + in_comma_array = false; + if (s->selector()) { + s->selector()->perform(this); + } + in_comma_array = was_comma_array; + append_string(")"); + in_wrapped = was; } } } - void Inspect::operator()(Selector_List_Ptr g) + void Inspect::operator()(SelectorList* g) { if (g->empty()) { @@ -1096,7 +1020,7 @@ namespace Sass { // probably ruby sass eqivalent of element_needs_parens if (output_style() == TO_SASS && g->length() == 1 && (!Cast((*g)[0]) && - !Cast((*g)[0]))) { + !Cast((*g)[0]))) { append_string("("); } else if (!in_declaration && in_comma_array) { @@ -1106,8 +1030,9 @@ namespace Sass { if (in_declaration) in_comma_array = true; for (size_t i = 0, L = g->length(); i < L; ++i) { + if (!in_wrapped && i == 0) append_indentation(); - if ((*g)[i] == 0) continue; + if ((*g)[i] == nullptr) continue; schedule_mapping(g->at(i)->last()); // add_open_mapping((*g)[i]->last()); (*g)[i]->perform(this); @@ -1122,7 +1047,7 @@ namespace Sass { // probably ruby sass eqivalent of element_needs_parens if (output_style() == TO_SASS && g->length() == 1 && (!Cast((*g)[0]) && - !Cast((*g)[0]))) { + !Cast((*g)[0]))) { append_string(",)"); } else if (!in_declaration && in_comma_array) { @@ -1130,9 +1055,70 @@ namespace Sass { } } + 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 (typeid(*item) == typeid(SelectorCombinator) || + typeid(*prev) == typeid(SelectorCombinator)) { + append_optional_space(); + } else { + append_mandatory_space(); + } + } + item->perform(this); + prev = item.ptr(); + } + } - void Inspect::fallback_impl(AST_Node_Ptr n) + void Inspect::operator()(SelectorComponent* sel) { + // You should probably never call this method directly + // But in case anyone does, we will do the upcasting + if (auto comp = Cast(sel)) operator()(comp); + if (auto comb = Cast(sel)) operator()(comb); + } + + void Inspect::operator()(CompoundSelector* sel) + { + if (sel->hasRealParent()) { + append_string("&"); + } + for (auto& item : sel->elements()) { + item->perform(this); + } + // Add the post line break (from ruby sass) + // Dart sass uses another logic for newlines + if (sel->hasPostLineBreak()) { + if (output_style() != COMPACT) { + append_optional_linefeed(); + } + } + } + + void Inspect::operator()(SelectorCombinator* sel) + { + append_optional_space(); + switch (sel->combinator()) { + case SelectorCombinator::Combinator::CHILD: append_string(">"); break; + case SelectorCombinator::Combinator::GENERAL: append_string("~"); break; + case SelectorCombinator::Combinator::ADJACENT: append_string("+"); break; + } + append_optional_space(); + // Add the post line break (from ruby sass) + // Dart sass uses another logic for newlines + if (sel->hasPostLineBreak()) { + if (output_style() != COMPACT) { + // append_optional_linefeed(); + } + } } } diff --git a/src/libsass/src/inspect.hpp b/src/libsass/src/inspect.hpp index c36790b80..a7aee00c7 100644 --- a/src/libsass/src/inspect.hpp +++ b/src/libsass/src/inspect.hpp @@ -13,90 +13,88 @@ namespace Sass { // import all the class-specific methods and override as desired using Operation_CRTP::operator(); - void fallback_impl(AST_Node_Ptr n); - public: Inspect(const Emitter& emi); virtual ~Inspect(); // statements - virtual void operator()(Block_Ptr); - virtual void operator()(Ruleset_Ptr); - virtual void operator()(Bubble_Ptr); - virtual void operator()(Supports_Block_Ptr); - virtual void operator()(Media_Block_Ptr); - virtual void operator()(At_Root_Block_Ptr); - virtual void operator()(Directive_Ptr); - virtual void operator()(Keyframe_Rule_Ptr); - virtual void operator()(Declaration_Ptr); - virtual void operator()(Assignment_Ptr); - virtual void operator()(Import_Ptr); - virtual void operator()(Import_Stub_Ptr); - virtual void operator()(Warning_Ptr); - virtual void operator()(Error_Ptr); - virtual void operator()(Debug_Ptr); - virtual void operator()(Comment_Ptr); - virtual void operator()(If_Ptr); - virtual void operator()(For_Ptr); - virtual void operator()(Each_Ptr); - virtual void operator()(While_Ptr); - virtual void operator()(Return_Ptr); - virtual void operator()(Extension_Ptr); - virtual void operator()(Definition_Ptr); - virtual void operator()(Mixin_Call_Ptr); - virtual void operator()(Content_Ptr); + virtual void operator()(Block*); + virtual void operator()(Ruleset*); + virtual void operator()(Bubble*); + virtual void operator()(Supports_Block*); + virtual void operator()(At_Root_Block*); + virtual void operator()(Directive*); + virtual void operator()(Keyframe_Rule*); + virtual void operator()(Declaration*); + virtual void operator()(Assignment*); + virtual void operator()(Import*); + virtual void operator()(Import_Stub*); + virtual void operator()(Warning*); + virtual void operator()(Error*); + virtual void operator()(Debug*); + virtual void operator()(Comment*); + virtual void operator()(If*); + virtual void operator()(For*); + virtual void operator()(Each*); + virtual void operator()(While*); + virtual void operator()(Return*); + virtual void operator()(ExtendRule*); + virtual void operator()(Definition*); + virtual void operator()(Mixin_Call*); + virtual void operator()(Content*); // expressions - virtual void operator()(Map_Ptr); - virtual void operator()(Function_Ptr); - virtual void operator()(List_Ptr); - virtual void operator()(Binary_Expression_Ptr); - virtual void operator()(Unary_Expression_Ptr); - virtual void operator()(Function_Call_Ptr); - virtual void operator()(Function_Call_Schema_Ptr); - // virtual void operator()(Custom_Warning_Ptr); - // virtual void operator()(Custom_Error_Ptr); - virtual void operator()(Variable_Ptr); - virtual void operator()(Number_Ptr); - virtual void operator()(Color_Ptr); - virtual void operator()(Boolean_Ptr); - virtual void operator()(String_Schema_Ptr); - virtual void operator()(String_Constant_Ptr); - virtual void operator()(String_Quoted_Ptr); - virtual void operator()(Custom_Error_Ptr); - virtual void operator()(Custom_Warning_Ptr); - virtual void operator()(Supports_Operator_Ptr); - virtual void operator()(Supports_Negation_Ptr); - virtual void operator()(Supports_Declaration_Ptr); - virtual void operator()(Supports_Interpolation_Ptr); - virtual void operator()(Media_Query_Ptr); - virtual void operator()(Media_Query_Expression_Ptr); - virtual void operator()(At_Root_Query_Ptr); - virtual void operator()(Null_Ptr); - virtual void operator()(Parent_Selector_Ptr p); + 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()(Supports_Operator*); + virtual void operator()(Supports_Negation*); + virtual void operator()(Supports_Declaration*); + virtual void operator()(Supports_Interpolation*); + virtual void operator()(MediaRule*); + virtual void operator()(CssMediaRule*); + virtual void operator()(CssMediaQuery*); + virtual void operator()(Media_Query*); + virtual void operator()(Media_Query_Expression*); + virtual void operator()(At_Root_Query*); + virtual void operator()(Null*); + virtual void operator()(Parent_Reference* p); // parameters and arguments - virtual void operator()(Parameter_Ptr); - virtual void operator()(Parameters_Ptr); - virtual void operator()(Argument_Ptr); - virtual void operator()(Arguments_Ptr); + virtual void operator()(Parameter*); + virtual void operator()(Parameters*); + virtual void operator()(Argument*); + virtual void operator()(Arguments*); // selectors - virtual void operator()(Selector_Schema_Ptr); - virtual void operator()(Placeholder_Selector_Ptr); - virtual void operator()(Element_Selector_Ptr); - virtual void operator()(Class_Selector_Ptr); - virtual void operator()(Id_Selector_Ptr); - virtual void operator()(Attribute_Selector_Ptr); - virtual void operator()(Pseudo_Selector_Ptr); - virtual void operator()(Wrapped_Selector_Ptr); - virtual void operator()(Compound_Selector_Ptr); - virtual void operator()(Complex_Selector_Ptr); - virtual void operator()(Selector_List_Ptr); - - virtual std::string lbracket(List_Ptr); - virtual std::string rbracket(List_Ptr); + virtual void operator()(Selector_Schema*); + virtual void operator()(Placeholder_Selector*); + virtual void operator()(Type_Selector*); + virtual void operator()(Class_Selector*); + virtual void operator()(Id_Selector*); + virtual void operator()(Attribute_Selector*); + virtual void operator()(Pseudo_Selector*); + virtual void operator()(SelectorComponent*); + virtual void operator()(SelectorCombinator*); + virtual void operator()(CompoundSelector*); + virtual void operator()(ComplexSelector*); + virtual void operator()(SelectorList*); + virtual std::string lbracket(List*); + virtual std::string rbracket(List*); - // template - // void fallback(U x) { fallback_impl(reinterpret_cast(x)); } }; } diff --git a/src/libsass/src/json.cpp b/src/libsass/src/json.cpp index 8f433f5d0..f7d06e4c7 100644 --- a/src/libsass/src/json.cpp +++ b/src/libsass/src/json.cpp @@ -402,7 +402,7 @@ char *json_encode_string(const char *str) try { emit_string(&sb, str); } - catch (std::exception) { + catch (std::exception&) { sb_free(&sb); throw; } @@ -421,7 +421,7 @@ char *json_stringify(const JsonNode *node, const char *space) else emit_value(&sb, node); } - catch (std::exception) { + catch (std::exception&) { sb_free(&sb); throw; } diff --git a/src/libsass/src/lexer.cpp b/src/libsass/src/lexer.cpp index be7f67713..bb02f9b85 100644 --- a/src/libsass/src/lexer.cpp +++ b/src/libsass/src/lexer.cpp @@ -1,9 +1,12 @@ +// sass.hpp must go before all system headers to get the +// __EXTENSIONS__ fix on Solaris. #include "sass.hpp" -#include + #include #include #include "lexer.hpp" #include "constants.hpp" +#include "util_string.hpp" namespace Sass { @@ -25,77 +28,14 @@ namespace Sass { const char* kwd_minus(const char* src) { return exactly<'-'>(src); }; const char* kwd_slash(const char* src) { return exactly<'/'>(src); }; - //#################################### - // implement some function that do exist in the standard - // but those are locale aware which brought some trouble - // this even seems to improve performance by quite a bit - //#################################### - - bool is_alpha(const char& chr) - { - return unsigned(chr - 'A') <= 'Z' - 'A' || - unsigned(chr - 'a') <= 'z' - 'a'; - } - - bool is_space(const char& chr) - { - // adapted the technique from is_alpha - return chr == ' ' || unsigned(chr - '\t') <= '\r' - '\t'; - } - - bool is_digit(const char& chr) - { - // adapted the technique from is_alpha - return unsigned(chr - '0') <= '9' - '0'; - } - - bool is_number(const char& chr) - { - // adapted the technique from is_alpha - return is_digit(chr) || chr == '-' || chr == '+'; - } - - bool is_xdigit(const char& chr) - { - // adapted the technique from is_alpha - return unsigned(chr - '0') <= '9' - '0' || - unsigned(chr - 'a') <= 'f' - 'a' || - unsigned(chr - 'A') <= 'F' - 'A'; - } - - bool is_punct(const char& chr) - { - // locale independent - return chr == '.'; - } - - bool is_alnum(const char& chr) - { - return is_alpha(chr) || is_digit(chr); - } - - // check if char is outside ascii range - bool is_unicode(const char& chr) - { - // check for unicode range - return unsigned(chr) > 127; - } - - // check if char is outside ascii range - // but with specific ranges (copied from Ruby Sass) - bool is_nonascii(const char& chr) - { - unsigned int cmp = unsigned(chr); - return ( - (cmp >= 128 && cmp <= 15572911) || - (cmp >= 15630464 && cmp <= 15712189) || - (cmp >= 4036001920) - ); + 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(const char& chr) + bool is_uri_character(char chr) { unsigned int cmp = unsigned(chr); return (cmp > 41 && cmp < 127) || @@ -104,17 +44,19 @@ namespace Sass { // check if char is within a reduced ascii range // valid for escaping (copied from Ruby Sass) - bool is_escapable_character(const char& chr) + bool is_escapable_character(char chr) { unsigned int cmp = unsigned(chr); return cmp > 31 && cmp < 127; } // Match word character (look ahead) - bool is_character(const char& chr) + bool is_character(char chr) { // valid alpha, numeric or unicode char (plus hyphen) - return is_alnum(chr) || is_unicode(chr) || chr == '-'; + return Util::ascii_isalnum(static_cast(chr)) || + !Util::ascii_isascii(static_cast(chr)) || + chr == '-'; } //#################################### @@ -122,16 +64,13 @@ namespace Sass { //#################################### // create matchers that advance the position - const char* space(const char* src) { return is_space(*src) ? src + 1 : 0; } - const char* alpha(const char* src) { return is_alpha(*src) ? src + 1 : 0; } - const char* unicode(const char* src) { return is_unicode(*src) ? src + 1 : 0; } - const char* nonascii(const char* src) { return is_nonascii(*src) ? src + 1 : 0; } - const char* digit(const char* src) { return is_digit(*src) ? src + 1 : 0; } - const char* xdigit(const char* src) { return is_xdigit(*src) ? src + 1 : 0; } - const char* alnum(const char* src) { return is_alnum(*src) ? src + 1 : 0; } - const char* punct(const char* src) { return is_punct(*src) ? src + 1 : 0; } - const char* hyphen(const char* src) { return *src && *src == '-' ? src + 1 : 0; } - const char* character(const char* src) { return is_character(*src) ? src + 1 : 0; } + 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; } @@ -150,11 +89,11 @@ namespace Sass { // 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?)/ + // Match linefeed /(?:\n|\r\n?|\f)/ const char* re_linebreak(const char* src) { // end of file or unix linefeed return here - if (*src == 0 || *src == '\n') return src + 1; + if (*src == 0 || *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 @@ -166,7 +105,7 @@ namespace Sass { const char* end_of_line(const char* src) { // end of file or unix linefeed return here - return *src == 0 || *src == '\n' || *src == '\r' ? src : 0; + return *src == 0 || *src == '\n' || *src == '\r' || *src == '\f' ? src : 0; } // Assert end_of_file boundary (/\z/) diff --git a/src/libsass/src/lexer.hpp b/src/libsass/src/lexer.hpp index 5838c291c..360ed2269 100644 --- a/src/libsass/src/lexer.hpp +++ b/src/libsass/src/lexer.hpp @@ -24,19 +24,11 @@ namespace Sass { // BASIC CLASS MATCHERS //#################################### - // These are locale independant - bool is_space(const char& src); - bool is_alpha(const char& src); - bool is_punct(const char& src); - bool is_digit(const char& src); - bool is_number(const char& src); - bool is_alnum(const char& src); - bool is_xdigit(const char& src); - bool is_unicode(const char& src); - bool is_nonascii(const char& src); - bool is_character(const char& src); - bool is_uri_character(const char& src); - bool escapable_character(const char& src); + // 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); @@ -44,11 +36,8 @@ namespace Sass { const char* digit(const char* src); const char* xdigit(const char* src); const char* alnum(const char* src); - const char* punct(const char* src); const char* hyphen(const char* src); - const char* unicode(const char* src); const char* nonascii(const char* src); - const char* character(const char* src); const char* uri_character(const char* src); const char* escapable_character(const char* src); diff --git a/src/libsass/src/listize.cpp b/src/libsass/src/listize.cpp index cb921ae67..41f5e7421 100644 --- a/src/libsass/src/listize.cpp +++ b/src/libsass/src/listize.cpp @@ -1,4 +1,7 @@ +// sass.hpp must go before all system headers to get the +// __EXTENSIONS__ fix on Solaris. #include "sass.hpp" + #include #include #include @@ -13,7 +16,13 @@ namespace Sass { Listize::Listize() { } - Expression_Ptr Listize::operator()(Selector_List_Ptr sel) + Expression* Listize::perform(AST_Node* node) + { + Listize listize; + return node->perform(&listize); + } + + Expression* Listize::operator()(SelectorList* sel) { List_Obj l = SASS_MEMORY_NEW(List, sel->pstate(), sel->length(), SASS_COMMA); l->from_selector(true); @@ -25,62 +34,37 @@ namespace Sass { return SASS_MEMORY_NEW(Null, l->pstate()); } - Expression_Ptr Listize::operator()(Compound_Selector_Ptr sel) + Expression* Listize::operator()(CompoundSelector* sel) { std::string str; for (size_t i = 0, L = sel->length(); i < L; ++i) { - Expression_Ptr e = (*sel)[i]->perform(this); + Expression* e = (*sel)[i]->perform(this); if (e) str += e->to_string(); } return SASS_MEMORY_NEW(String_Quoted, sel->pstate(), str); } - Expression_Ptr Listize::operator()(Complex_Selector_Ptr sel) + Expression* Listize::operator()(ComplexSelector* sel) { - List_Obj l = SASS_MEMORY_NEW(List, sel->pstate(), 2); + List_Obj l = SASS_MEMORY_NEW(List, sel->pstate()); + // ToDo: investigate what this does + // Note: seems reated to parent ref l->from_selector(true); - Compound_Selector_Obj head = sel->head(); - if (head && !head->is_empty_reference()) - { - Expression_Ptr hh = head->perform(this); - if (hh) l->append(hh); - } - std::string reference = ! sel->reference() ? "" - : sel->reference()->to_string(); - switch(sel->combinator()) - { - case Complex_Selector::PARENT_OF: - l->append(SASS_MEMORY_NEW(String_Quoted, sel->pstate(), ">")); - break; - case Complex_Selector::ADJACENT_TO: - l->append(SASS_MEMORY_NEW(String_Quoted, sel->pstate(), "+")); - break; - case Complex_Selector::REFERENCE: - l->append(SASS_MEMORY_NEW(String_Quoted, sel->pstate(), "/" + reference + "/")); - break; - case Complex_Selector::PRECEDES: - l->append(SASS_MEMORY_NEW(String_Quoted, sel->pstate(), "~")); - break; - case Complex_Selector::ANCESTOR_OF: - break; - default: break; + for (auto component : sel->elements()) { + if (CompoundSelectorObj compound = Cast(component)) { + if (!compound->empty()) { + Expression_Obj hh = compound->perform(this); + if (hh) l->append(hh); + } + } + else if (component) { + l->append(SASS_MEMORY_NEW(String_Quoted, component->pstate(), component->to_string())); + } } - Complex_Selector_Obj tail = sel->tail(); - if (tail) - { - Expression_Obj tt = tail->perform(this); - if (List_Ptr ls = Cast(tt)) - { l->concat(ls); } - } if (l->length() == 0) return 0; return l.detach(); } - Expression_Ptr Listize::fallback_impl(AST_Node_Ptr n) - { - return Cast(n); - } - } diff --git a/src/libsass/src/listize.hpp b/src/libsass/src/listize.hpp index 9716ebefc..5da094df9 100644 --- a/src/libsass/src/listize.hpp +++ b/src/libsass/src/listize.hpp @@ -1,32 +1,35 @@ #ifndef SASS_LISTIZE_H #define SASS_LISTIZE_H -#include -#include +// sass.hpp must go before all system headers to get the +// __EXTENSIONS__ fix on Solaris. +#include "sass.hpp" -#include "ast.hpp" -#include "context.hpp" +#include "ast_fwd_decl.hpp" #include "operation.hpp" -#include "environment.hpp" namespace Sass { struct Backtrace; - class Listize : public Operation_CRTP { + class Listize : public Operation_CRTP { - Expression_Ptr fallback_impl(AST_Node_Ptr n); + public: + + static Expression* perform(AST_Node* node); public: Listize(); ~Listize() { } - Expression_Ptr operator()(Selector_List_Ptr); - Expression_Ptr operator()(Complex_Selector_Ptr); - Expression_Ptr operator()(Compound_Selector_Ptr); + Expression* operator()(SelectorList*); + Expression* operator()(ComplexSelector*); + Expression* operator()(CompoundSelector*); + // generic fallback template - Expression_Ptr fallback(U x) { return fallback_impl(x); } + Expression* fallback(U x) + { return Cast(x); } }; } diff --git a/src/libsass/src/memory/SharedPtr.cpp b/src/libsass/src/memory/SharedPtr.cpp index 2530360a5..db0ff1d41 100644 --- a/src/libsass/src/memory/SharedPtr.cpp +++ b/src/libsass/src/memory/SharedPtr.cpp @@ -18,7 +18,7 @@ namespace Sass { std::cerr << "# REPORTING MISSING DEALLOCATIONS #\n"; std::cerr << "###################################\n"; for (SharedObj* var : all) { - if (AST_Node_Ptr ast = dynamic_cast(var)) { + if (AST_Node* ast = dynamic_cast(var)) { debug_ast(ast); } else { std::cerr << "LEAKED " << var << "\n"; @@ -30,85 +30,4 @@ namespace Sass { #endif bool SharedObj::taint = false; - - SharedObj::SharedObj() - : detached(false) - #ifdef DEBUG_SHARED_PTR - , dbg(false) - #endif - { - refcounter = 0; - #ifdef DEBUG_SHARED_PTR - if (taint) all.push_back(this); - #endif - }; - - SharedObj::~SharedObj() { - #ifdef DEBUG_SHARED_PTR - if (dbg) std::cerr << "Destruct " << this << "\n"; - if(!all.empty()) { // check needed for MSVC (no clue why?) - all.erase(std::remove(all.begin(), all.end(), this), all.end()); - } - #endif - }; - - void SharedPtr::decRefCount() { - if (node) { - -- node->refcounter; - #ifdef DEBUG_SHARED_PTR - if (node->dbg) std::cerr << "- " << node << " X " << node->refcounter << " (" << this << ") " << "\n"; - #endif - if (node->refcounter == 0) { - #ifdef DEBUG_SHARED_PTR - // AST_Node_Ptr ast = dynamic_cast(node); - if (node->dbg) std::cerr << "DELETE NODE " << node << "\n"; - #endif - if (!node->detached) { - delete(node); - } - } - } - } - - void SharedPtr::incRefCount() { - if (node) { - ++ node->refcounter; - node->detached = false; - #ifdef DEBUG_SHARED_PTR - if (node->dbg) { - std::cerr << "+ " << node << " X " << node->refcounter << " (" << this << ") " << "\n"; - } - #endif - } - } - - SharedPtr::~SharedPtr() { - decRefCount(); - } - - - // the create constructor - SharedPtr::SharedPtr(SharedObj* ptr) - : node(ptr) { - incRefCount(); - } - // copy assignment operator - SharedPtr& SharedPtr::operator=(const SharedPtr& rhs) { - void* cur_ptr = (void*) node; - void* rhs_ptr = (void*) rhs.node; - if (cur_ptr == rhs_ptr) { - return *this; - } - decRefCount(); - node = rhs.node; - incRefCount(); - return *this; - } - - // the copy constructor - SharedPtr::SharedPtr(const SharedPtr& obj) - : node(obj.node) { - incRefCount(); - } - -} \ No newline at end of file +} diff --git a/src/libsass/src/memory/SharedPtr.hpp b/src/libsass/src/memory/SharedPtr.hpp index f20dfa39b..3a2c2bf4c 100644 --- a/src/libsass/src/memory/SharedPtr.hpp +++ b/src/libsass/src/memory/SharedPtr.hpp @@ -3,8 +3,16 @@ #include "sass/base.h" +#include +#include +#include +#include #include +// https://lokiastari.com/blog/2014/12/30/c-plus-plus-by-example-smart-pointer/index.html +// https://lokiastari.com/blog/2015/01/15/c-plus-plus-by-example-smart-pointer-part-ii/index.html +// https://lokiastari.com/blog/2015/01/23/c-plus-plus-by-example-smart-pointer-part-iii/index.html + namespace Sass { class SharedPtr; @@ -38,169 +46,275 @@ namespace Sass { #endif + // SharedObj is the base class for all objects that can be stored as a shared object + // It adds the reference counter and other values directly to the objects + // This gives a slight overhead when directly used as a stack object, but has some + // advantages for our code. It is safe to create two shared pointers from the same + // objects, as the "control block" is directly attached to it. This would lead + // to undefined behavior with std::shared_ptr. This also avoids the need to + // allocate additional control blocks and/or the need to dereference two + // pointers on each operation. This can be optimized in `std::shared_ptr` + // too by using `std::make_shared` (where the control block and the actual + // object are allocated in one continuous memory block via one single call). class SharedObj { - protected: - friend class SharedPtr; - friend class Memory_Manager; + public: + SharedObj() : refcount(0), detached(false) { + #ifdef DEBUG_SHARED_PTR + if (taint) all.push_back(this); + #endif + } + virtual ~SharedObj() { + #ifdef DEBUG_SHARED_PTR + for (size_t i = 0; i < all.size(); i++) { + if (all[i] == this) { + all.erase(all.begin() + i); + break; + } + } + #endif + } + #ifdef DEBUG_SHARED_PTR - static std::vector all; - std::string file; - size_t line; + static void dumpMemLeaks(); + SharedObj* trace(std::string file, size_t line) { + this->file = file; + this->line = line; + return this; + } + std::string getDbgFile() { return file; } + size_t getDbgLine() { return line; } + void setDbg(bool dbg) { this->dbg = dbg; } + size_t getRefCount() const { return refcount; } #endif - static bool taint; - long refcounter; - // long refcount; + + static void setTaint(bool val) { taint = val; } + + virtual std::string to_string() const = 0; + protected: + friend class SharedPtr; + friend class Memory_Manager; + size_t refcount; bool detached; + static bool taint; #ifdef DEBUG_SHARED_PTR - bool dbg; + std::string file; + size_t line; + bool dbg = false; + static std::vector all; #endif - public: - #ifdef DEBUG_SHARED_PTR - static void dumpMemLeaks(); - SharedObj* trace(std::string file, size_t line) { - this->file = file; - this->line = line; - return this; + }; + + // SharedPtr is a intermediate (template-less) base class for SharedImpl. + // ToDo: there should be a way to include this in SharedImpl and to get + // ToDo: rid of all the static_cast that are now needed in SharedImpl. + class SharedPtr { + public: + SharedPtr() : node(nullptr) {} + SharedPtr(SharedObj* ptr) : node(ptr) { + incRefCount(); + } + SharedPtr(const SharedPtr& obj) : SharedPtr(obj.node) {} + ~SharedPtr() { + decRefCount(); + } + + SharedPtr& operator=(SharedObj* other_node) { + if (node != other_node) { + decRefCount(); + node = other_node; + incRefCount(); + } else if (node != nullptr) { + node->detached = false; } - #endif - SharedObj(); - #ifdef DEBUG_SHARED_PTR - std::string getDbgFile() { - return file; + return *this; + } + + SharedPtr& operator=(const SharedPtr& obj) { + return *this = obj.node; + } + + // Prevents all SharedPtrs from freeing this node until it is assigned to another SharedPtr. + SharedObj* detach() { + if (node != nullptr) node->detached = true; + #ifdef DEBUG_SHARED_PTR + if (node->dbg) { + std::cerr << "DETACHING NODE\n"; } - size_t getDbgLine() { - return line; + #endif + return node; + } + + SharedObj* obj() const { return node; } + SharedObj* operator->() const { return node; } + bool isNull() const { return node == nullptr; } + operator bool() const { return node != nullptr; } + + protected: + SharedObj* node; + void decRefCount() { + if (node == nullptr) return; + --node->refcount; + #ifdef DEBUG_SHARED_PTR + if (node->dbg) std::cerr << "- " << node << " X " << node->refcount << " (" << this << ") " << "\n"; + #endif + if (node->refcount == 0 && !node->detached) { + #ifdef DEBUG_SHARED_PTR + if (node->dbg) std::cerr << "DELETE NODE " << node << "\n"; + #endif + delete node; } - void setDbg(bool dbg) { - this->dbg = dbg; + else if (node->refcount == 0) { + #ifdef DEBUG_SHARED_PTR + if (node->dbg) std::cerr << "NODE EVAEDED DELETE " << node << "\n"; + #endif } - #endif - static void setTaint(bool val) { - taint = val; } - virtual ~SharedObj(); - long getRefCount() { - return refcounter; + void incRefCount() { + if (node == nullptr) return; + node->detached = false; + ++node->refcount; + #ifdef DEBUG_SHARED_PTR + if (node->dbg) std::cerr << "+ " << node << " X " << node->refcount << " (" << this << ") " << "\n"; + #endif } }; + template + class SharedImpl : private SharedPtr { - class SharedPtr { - protected: - SharedObj* node; - protected: - void decRefCount(); - void incRefCount(); - public: - // the empty constructor - SharedPtr() - : node(NULL) {}; - // the create constructor - SharedPtr(SharedObj* ptr); - // the copy constructor - SharedPtr(const SharedPtr& obj); - // the move constructor - SharedPtr(SharedPtr&& obj); - // copy assignment operator - SharedPtr& operator=(const SharedPtr& obj); - // move assignment operator - SharedPtr& operator=(SharedPtr&& obj); - // pure virtual destructor - virtual ~SharedPtr() = 0; public: - SharedObj* obj () const { - return node; - }; - SharedObj* operator-> () const { - return node; - }; - bool isNull () { - return node == NULL; - }; - bool isNull () const { - return node == NULL; - }; - SharedObj* detach() const { - if (node) { - node->detached = true; - } - return node; - }; - operator bool() const { - return node != NULL; - }; + SharedImpl() : SharedPtr(nullptr) {} - }; + template + SharedImpl(U* node) : + SharedPtr(static_cast(node)) {} - template < class T > - class SharedImpl : private SharedPtr { - public: - SharedImpl() - : SharedPtr(NULL) {}; - SharedImpl(T* node) - : SharedPtr(node) {}; - template < class U > - SharedImpl(SharedImpl obj) - : SharedPtr(static_cast(obj.ptr())) {} - SharedImpl(T&& node) - : SharedPtr(node) {}; - SharedImpl(const T& node) - : SharedPtr(node) {}; - // the copy constructor - SharedImpl(const SharedImpl& impl) - : SharedPtr(impl.node) {}; - // the move constructor - SharedImpl(SharedImpl&& impl) - : SharedPtr(impl.node) {}; - // copy assignment operator - SharedImpl& operator=(const SharedImpl& rhs) { - if (node) decRefCount(); - node = rhs.node; - incRefCount(); - return *this; - } - // move assignment operator - SharedImpl& operator=(SharedImpl&& rhs) { - // don't move our self - if (this != &rhs) { - if (node) decRefCount(); - node = std::move(rhs.node); - rhs.node = NULL; - } - return *this; - } - ~SharedImpl() {}; - public: - operator T*() const { - return static_cast(this->obj()); - } - operator T&() const { - return *static_cast(this->obj()); + template + SharedImpl(const SharedImpl& impl) : + SharedImpl(impl.ptr()) {} + + template + SharedImpl& operator=(U *rhs) { + return static_cast&>( + SharedPtr::operator=(static_cast(rhs))); } - T& operator* () const { - return *static_cast(this->obj()); - }; - T* operator-> () const { - return static_cast(this->obj()); - }; - T* ptr () const { - return static_cast(this->obj()); - }; - T* detach() const { - if (this->obj() == NULL) return NULL; - return static_cast(SharedPtr::detach()); + + template + SharedImpl& operator=(const SharedImpl& rhs) { + return static_cast&>( + SharedPtr::operator=(static_cast&>(rhs))); } - bool isNull() const { - return this->obj() == NULL; + + operator std::string() const { + if (node) return node->to_string(); + return "null"; } - bool operator<(const T& rhs) const { - return *this->ptr() < rhs; - }; - operator bool() const { - return this->obj() != NULL; - }; + + using SharedPtr::isNull; + using SharedPtr::operator bool; + operator T*() const { return static_cast(this->obj()); } + operator T&() const { return *static_cast(this->obj()); } + T& operator* () const { return *static_cast(this->obj()); }; + T* operator-> () const { return static_cast(this->obj()); }; + T* ptr () const { return static_cast(this->obj()); }; + T* detach() { return static_cast(SharedPtr::detach()); } + }; -} + // Comparison operators, based on: + // https://en.cppreference.com/w/cpp/memory/unique_ptr/operator_cmp + + template + bool operator==(const SharedImpl& x, const SharedImpl& y) { + return x.ptr() == y.ptr(); + } + + template + bool operator!=(const SharedImpl& x, const SharedImpl& y) { + return x.ptr() != y.ptr(); + } + + template + bool operator<(const SharedImpl& x, const SharedImpl& y) { + using CT = typename std::common_type::type; + return std::less()(x.get(), y.get()); + } + + template + bool operator<=(const SharedImpl& x, const SharedImpl& y) { + return !(y < x); + } + + template + bool operator>(const SharedImpl& x, const SharedImpl& y) { + return y < x; + } + + template + bool operator>=(const SharedImpl& x, const SharedImpl& y) { + return !(x < y); + } + + template + bool operator==(const SharedImpl& x, std::nullptr_t) noexcept { + return x.isNull(); + } + + template + bool operator==(std::nullptr_t, const SharedImpl& x) noexcept { + return x.isNull(); + } + + template + bool operator!=(const SharedImpl& x, std::nullptr_t) noexcept { + return !x.isNull(); + } + + template + bool operator!=(std::nullptr_t, const SharedImpl& x) noexcept { + return !x.isNull(); + } + + template + bool operator<(const SharedImpl& x, std::nullptr_t) { + return std::less()(x.get(), nullptr); + } + + template + bool operator<(std::nullptr_t, const SharedImpl& y) { + return std::less()(nullptr, y.get()); + } + + template + bool operator<=(const SharedImpl& x, std::nullptr_t) { + return !(nullptr < x); + } + + template + bool operator<=(std::nullptr_t, const SharedImpl& y) { + return !(y < nullptr); + } + + template + bool operator>(const SharedImpl& x, std::nullptr_t) { + return nullptr < x; + } + + template + bool operator>(std::nullptr_t, const SharedImpl& y) { + return y < nullptr; + } + + template + bool operator>=(const SharedImpl& x, std::nullptr_t) { + return !(x < nullptr); + } + + template + bool operator>=(std::nullptr_t, const SharedImpl& y) { + return !(nullptr < y); + } + +} // namespace Sass -#endif \ No newline at end of file +#endif diff --git a/src/libsass/src/node.cpp b/src/libsass/src/node.cpp deleted file mode 100644 index 08eada733..000000000 --- a/src/libsass/src/node.cpp +++ /dev/null @@ -1,319 +0,0 @@ -#include "sass.hpp" -#include - -#include "node.hpp" -#include "context.hpp" -#include "parser.hpp" - -namespace Sass { - - - Node Node::createCombinator(const Complex_Selector::Combinator& combinator) { - NodeDequePtr null; - return Node(COMBINATOR, combinator, NULL /*pSelector*/, null /*pCollection*/); - } - - - Node Node::createSelector(const Complex_Selector& pSelector) { - NodeDequePtr null; - - Complex_Selector_Ptr pStripped = SASS_MEMORY_COPY(&pSelector); - pStripped->tail(NULL); - pStripped->combinator(Complex_Selector::ANCESTOR_OF); - - Node n(SELECTOR, Complex_Selector::ANCESTOR_OF, pStripped, null /*pCollection*/); - n.got_line_feed = pSelector.has_line_feed(); - return n; - } - - - Node Node::createCollection() { - NodeDequePtr pEmptyCollection = std::make_shared(); - return Node(COLLECTION, Complex_Selector::ANCESTOR_OF, NULL /*pSelector*/, pEmptyCollection); - } - - - Node Node::createCollection(const NodeDeque& values) { - NodeDequePtr pShallowCopiedCollection = std::make_shared(values); - return Node(COLLECTION, Complex_Selector::ANCESTOR_OF, NULL /*pSelector*/, pShallowCopiedCollection); - } - - - Node Node::createNil() { - NodeDequePtr null; - return Node(NIL, Complex_Selector::ANCESTOR_OF, NULL /*pSelector*/, null /*pCollection*/); - } - - - Node::Node(const TYPE& type, Complex_Selector::Combinator combinator, Complex_Selector_Ptr pSelector, NodeDequePtr& pCollection) - : got_line_feed(false), mType(type), mCombinator(combinator), mpSelector(pSelector), mpCollection(pCollection) - { if (pSelector) got_line_feed = pSelector->has_line_feed(); } - - - Node Node::klone() const { - NodeDequePtr pNewCollection = std::make_shared(); - if (mpCollection) { - for (NodeDeque::iterator iter = mpCollection->begin(), iterEnd = mpCollection->end(); iter != iterEnd; iter++) { - Node& toClone = *iter; - pNewCollection->push_back(toClone.klone()); - } - } - - Node n(mType, mCombinator, mpSelector ? SASS_MEMORY_COPY(mpSelector) : NULL, pNewCollection); - n.got_line_feed = got_line_feed; - return n; - } - - - bool Node::contains(const Node& potentialChild) const { - bool found = false; - - for (NodeDeque::iterator iter = mpCollection->begin(), iterEnd = mpCollection->end(); iter != iterEnd; iter++) { - Node& toTest = *iter; - - if (toTest == potentialChild) { - found = true; - break; - } - } - - return found; - } - - - bool Node::operator==(const Node& rhs) const { - if (this->type() != rhs.type()) { - return false; - } - - if (this->isCombinator()) { - - return this->combinator() == rhs.combinator(); - - } else if (this->isNil()) { - - return true; // no state to check - - } else if (this->isSelector()){ - - return *this->selector() == *rhs.selector(); - - } else if (this->isCollection()) { - - if (this->collection()->size() != rhs.collection()->size()) { - return false; - } - - for (NodeDeque::iterator lhsIter = this->collection()->begin(), lhsIterEnd = this->collection()->end(), - rhsIter = rhs.collection()->begin(); lhsIter != lhsIterEnd; lhsIter++, rhsIter++) { - - if (*lhsIter != *rhsIter) { - return false; - } - - } - - return true; - - } - - // We shouldn't get here. - throw "Comparing unknown node types. A new type was probably added and this method wasn't implemented for it."; - } - - - void Node::plus(Node& rhs) { - if (!this->isCollection() || !rhs.isCollection()) { - throw "Both the current node and rhs must be collections."; - } - this->collection()->insert(this->collection()->end(), rhs.collection()->begin(), rhs.collection()->end()); - } - -#ifdef DEBUG - std::ostream& operator<<(std::ostream& os, const Node& node) { - - if (node.isCombinator()) { - - switch (node.combinator()) { - case Complex_Selector::ANCESTOR_OF: os << "\" \""; break; - case Complex_Selector::PARENT_OF: os << "\">\""; break; - case Complex_Selector::PRECEDES: os << "\"~\""; break; - case Complex_Selector::ADJACENT_TO: os << "\"+\""; break; - case Complex_Selector::REFERENCE: os << "\"/\""; break; - } - - } else if (node.isNil()) { - - os << "nil"; - - } else if (node.isSelector()){ - - os << node.selector()->head()->to_string(); - - } else if (node.isCollection()) { - - os << "["; - - for (NodeDeque::iterator iter = node.collection()->begin(), iterBegin = node.collection()->begin(), iterEnd = node.collection()->end(); iter != iterEnd; iter++) { - if (iter != iterBegin) { - os << ", "; - } - - os << (*iter); - } - - os << "]"; - - } - - return os; - - } -#endif - - - Node complexSelectorToNode(Complex_Selector_Ptr pToConvert) { - if (pToConvert == NULL) { - return Node::createNil(); - } - Node node = Node::createCollection(); - node.got_line_feed = pToConvert->has_line_feed(); - bool has_lf = pToConvert->has_line_feed(); - - // unwrap the selector from parent ref - if (pToConvert->head() && pToConvert->head()->has_parent_ref()) { - Complex_Selector_Obj tail = pToConvert->tail(); - if (tail) tail->has_line_feed(pToConvert->has_line_feed()); - pToConvert = tail; - } - - while (pToConvert) { - - bool empty_parent_ref = pToConvert->head() && pToConvert->head()->is_empty_reference(); - - // the first Complex_Selector may contain a dummy head pointer, skip it. - if (pToConvert->head() && !empty_parent_ref) { - node.collection()->push_back(Node::createSelector(*pToConvert)); - if (has_lf) node.collection()->back().got_line_feed = has_lf; - if (pToConvert->head() || empty_parent_ref) { - if (pToConvert->tail()) { - pToConvert->tail()->has_line_feed(pToConvert->has_line_feed()); - } - } - has_lf = false; - } - - if (pToConvert->combinator() != Complex_Selector::ANCESTOR_OF) { - node.collection()->push_back(Node::createCombinator(pToConvert->combinator())); - if (has_lf) node.collection()->back().got_line_feed = has_lf; - has_lf = false; - } - - if (pToConvert && empty_parent_ref && pToConvert->tail()) { - // pToConvert->tail()->has_line_feed(pToConvert->has_line_feed()); - } - - pToConvert = pToConvert->tail(); - } - - return node; - } - - - Complex_Selector_Ptr nodeToComplexSelector(const Node& toConvert) { - if (toConvert.isNil()) { - return NULL; - } - - - if (!toConvert.isCollection()) { - throw "The node to convert to a Complex_Selector_Ptr must be a collection type or nil."; - } - - - NodeDeque& childNodes = *toConvert.collection(); - - std::string noPath(""); - Complex_Selector_Obj pFirst = SASS_MEMORY_NEW(Complex_Selector, ParserState("[NODE]"), Complex_Selector::ANCESTOR_OF, NULL, NULL); - - Complex_Selector_Obj pCurrent = pFirst; - - if (toConvert.isSelector()) pFirst->has_line_feed(toConvert.got_line_feed); - if (toConvert.isCombinator()) pFirst->has_line_feed(toConvert.got_line_feed); - - for (NodeDeque::iterator childIter = childNodes.begin(), childIterEnd = childNodes.end(); childIter != childIterEnd; childIter++) { - - Node& child = *childIter; - - if (child.isSelector()) { - // JMA - need to clone the selector, because they can end up getting shared across Node - // collections, and can result in an infinite loop during the call to parentSuperselector() - pCurrent->tail(SASS_MEMORY_COPY(child.selector())); - // if (child.got_line_feed) pCurrent->has_line_feed(child.got_line_feed); - pCurrent = pCurrent->tail(); - } else if (child.isCombinator()) { - pCurrent->combinator(child.combinator()); - if (child.got_line_feed) pCurrent->has_line_feed(child.got_line_feed); - - // if the next node is also a combinator, create another Complex_Selector to hold it so it doesn't replace the current combinator - if (childIter+1 != childIterEnd) { - Node& nextNode = *(childIter+1); - if (nextNode.isCombinator()) { - pCurrent->tail(SASS_MEMORY_NEW(Complex_Selector, ParserState("[NODE]"), Complex_Selector::ANCESTOR_OF, NULL, NULL)); - if (nextNode.got_line_feed) pCurrent->tail()->has_line_feed(nextNode.got_line_feed); - pCurrent = pCurrent->tail(); - } - } - } else { - throw "The node to convert's children must be only combinators or selectors."; - } - } - - // Put the dummy Compound_Selector in the first position, for consistency with the rest of libsass - Compound_Selector_Ptr fakeHead = SASS_MEMORY_NEW(Compound_Selector, ParserState("[NODE]"), 1); - Parent_Selector_Ptr selectorRef = SASS_MEMORY_NEW(Parent_Selector, ParserState("[NODE]")); - fakeHead->elements().push_back(selectorRef); - if (toConvert.got_line_feed) pFirst->has_line_feed(toConvert.got_line_feed); - // pFirst->has_line_feed(pFirst->has_line_feed() || pFirst->tail()->has_line_feed() || toConvert.got_line_feed); - pFirst->head(fakeHead); - return SASS_MEMORY_COPY(pFirst); - } - - // A very naive trim function, which removes duplicates in a node - // This is only used in Complex_Selector::unify_with for now, may need modifications to fit other needs - Node Node::naiveTrim(Node& seqses) { - - std::vector res; - std::vector known; - - NodeDeque::reverse_iterator seqsesIter = seqses.collection()->rbegin(), - seqsesIterEnd = seqses.collection()->rend(); - - for (; seqsesIter != seqsesIterEnd; ++seqsesIter) - { - Node& seqs1 = *seqsesIter; - if( seqs1.isSelector() ) { - Complex_Selector_Obj sel = seqs1.selector(); - std::vector::iterator it; - bool found = false; - for (it = known.begin(); it != known.end(); ++it) { - if (**it == *sel) { found = true; break; } - } - if( !found ) { - known.push_back(seqs1.selector()); - res.push_back(&seqs1); - } - } else { - res.push_back(&seqs1); - } - } - - Node result = Node::createCollection(); - - for (size_t i = res.size() - 1; i != std::string::npos; --i) { - result.collection()->push_back(*res[i]); - } - - return result; - } -} diff --git a/src/libsass/src/node.hpp b/src/libsass/src/node.hpp deleted file mode 100644 index 23ba360c3..000000000 --- a/src/libsass/src/node.hpp +++ /dev/null @@ -1,118 +0,0 @@ -#ifndef SASS_NODE_H -#define SASS_NODE_H - -#include -#include - -#include "ast.hpp" - - -namespace Sass { - - - - - class Context; - - /* - There are a lot of stumbling blocks when trying to port the ruby extend code to C++. The biggest is the choice of - data type. The ruby code will pretty seamlessly switch types between an Array (libsass' - equivalent is the Complex_Selector) to a Sequence, which contains more metadata about the sequence than just the - selector info. They also have the ability to have arbitrary nestings of arrays like [1, [2]], which is hard to - implement using Array equivalents in C++ (like the deque or vector). They also have the ability to include nil - in the arrays, like [1, nil, 3], which has potential semantic differences than an empty array [1, [], 3]. To be - able to represent all of these as unique cases, we need to create a tree of variant objects. The tree nature allows - the inconsistent nesting levels. The variant nature (while making some of the C++ code uglier) allows the code to - more closely match the ruby code, which is a huge benefit when attempting to implement an complex algorithm like - the Extend operator. - - Note that the current libsass data model also pairs the combinator with the Complex_Selector that follows it, but - ruby sass has no such restriction, so we attempt to create a data structure that can handle them split apart. - */ - - class Node; - typedef std::deque NodeDeque; - typedef std::shared_ptr NodeDequePtr; - - class Node { - public: - enum TYPE { - SELECTOR, - COMBINATOR, - COLLECTION, - NIL - }; - - TYPE type() const { return mType; } - bool isCombinator() const { return mType == COMBINATOR; } - bool isSelector() const { return mType == SELECTOR; } - bool isCollection() const { return mType == COLLECTION; } - bool isNil() const { return mType == NIL; } - bool got_line_feed; - - Complex_Selector::Combinator combinator() const { return mCombinator; } - - Complex_Selector_Obj selector() { return mpSelector; } - Complex_Selector_Obj selector() const { return mpSelector; } - - NodeDequePtr collection() { return mpCollection; } - const NodeDequePtr collection() const { return mpCollection; } - - static Node createCombinator(const Complex_Selector::Combinator& combinator); - - // This method will klone the selector, stripping off the tail and combinator - static Node createSelector(const Complex_Selector& pSelector); - - static Node createCollection(); - static Node createCollection(const NodeDeque& values); - - static Node createNil(); - static Node naiveTrim(Node& seqses); - - Node klone() const; - - bool operator==(const Node& rhs) const; - inline bool operator!=(const Node& rhs) const { return !(*this == rhs); } - - - /* - COLLECTION FUNCTIONS - - Most types don't need any helper methods (nil and combinator due to their simplicity and - selector due to the fact that we leverage the non-node selector code on the Complex_Selector - whereever possible). The following methods are intended to be called on Node objects whose - type is COLLECTION only. - */ - - // rhs and this must be node collections. Shallow copy the nodes from rhs to the end of this. - // This function DOES NOT remove the nodes from rhs. - void plus(Node& rhs); - - // potentialChild must be a node collection of selectors/combinators. this must be a collection - // of collections of nodes/combinators. This method checks if potentialChild is a child of this - // Node. - bool contains(const Node& potentialChild) const; - - private: - // Private constructor; Use the static methods (like createCombinator and createSelector) - // to instantiate this object. This is more expressive, and it allows us to break apart each - // case into separate functions. - Node(const TYPE& type, Complex_Selector::Combinator combinator, Complex_Selector_Ptr pSelector, NodeDequePtr& pCollection); - - TYPE mType; - - // TODO: can we union these to save on memory? - Complex_Selector::Combinator mCombinator; - Complex_Selector_Obj mpSelector; - NodeDequePtr mpCollection; - }; - -#ifdef DEBUG - std::ostream& operator<<(std::ostream& os, const Node& node); -#endif - Node complexSelectorToNode(Complex_Selector_Ptr pToConvert); - Complex_Selector_Ptr nodeToComplexSelector(const Node& toConvert); - -} - -#endif diff --git a/src/libsass/src/operation.hpp b/src/libsass/src/operation.hpp index 2d4fbec9d..007a7e96a 100644 --- a/src/libsass/src/operation.hpp +++ b/src/libsass/src/operation.hpp @@ -1,171 +1,221 @@ #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 std::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 Supports_Condition* 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 std::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 Supports_Condition* perform(Operation* op) override { return (*op)(this); } \ + template class Operation { public: - virtual T operator()(AST_Node_Ptr x) = 0; - virtual ~Operation() { } + virtual T operator()(AST_Node* x) = 0; // statements - virtual T operator()(Block_Ptr x) = 0; - virtual T operator()(Ruleset_Ptr x) = 0; - virtual T operator()(Bubble_Ptr x) = 0; - virtual T operator()(Trace_Ptr x) = 0; - virtual T operator()(Supports_Block_Ptr x) = 0; - virtual T operator()(Media_Block_Ptr x) = 0; - virtual T operator()(At_Root_Block_Ptr x) = 0; - virtual T operator()(Directive_Ptr x) = 0; - virtual T operator()(Keyframe_Rule_Ptr x) = 0; - virtual T operator()(Declaration_Ptr x) = 0; - virtual T operator()(Assignment_Ptr x) = 0; - virtual T operator()(Import_Ptr x) = 0; - virtual T operator()(Import_Stub_Ptr x) = 0; - virtual T operator()(Warning_Ptr x) = 0; - virtual T operator()(Error_Ptr x) = 0; - virtual T operator()(Debug_Ptr x) = 0; - virtual T operator()(Comment_Ptr x) = 0; - virtual T operator()(If_Ptr x) = 0; - virtual T operator()(For_Ptr x) = 0; - virtual T operator()(Each_Ptr x) = 0; - virtual T operator()(While_Ptr x) = 0; - virtual T operator()(Return_Ptr x) = 0; - virtual T operator()(Content_Ptr x) = 0; - virtual T operator()(Extension_Ptr x) = 0; - virtual T operator()(Definition_Ptr x) = 0; - virtual T operator()(Mixin_Call_Ptr x) = 0; + virtual T operator()(Block* x) = 0; + virtual T operator()(Ruleset* x) = 0; + virtual T operator()(Bubble* x) = 0; + virtual T operator()(Trace* x) = 0; + virtual T operator()(Supports_Block* x) = 0; + virtual T operator()(MediaRule* x) = 0; + virtual T operator()(CssMediaRule* x) = 0; + virtual T operator()(CssMediaQuery* x) = 0; + virtual T operator()(At_Root_Block* x) = 0; + virtual T operator()(Directive* x) = 0; + virtual T operator()(Keyframe_Rule* x) = 0; + 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()(Warning* x) = 0; + virtual T operator()(Error* x) = 0; + virtual T operator()(Debug* x) = 0; + virtual T operator()(Comment* x) = 0; + virtual T operator()(If* x) = 0; + virtual T operator()(For* x) = 0; + virtual T operator()(Each* x) = 0; + virtual T operator()(While* 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()(List_Ptr x) = 0; - virtual T operator()(Map_Ptr x) = 0; - virtual T operator()(Function_Ptr x) = 0; - virtual T operator()(Binary_Expression_Ptr x) = 0; - virtual T operator()(Unary_Expression_Ptr x) = 0; - virtual T operator()(Function_Call_Ptr x) = 0; - virtual T operator()(Function_Call_Schema_Ptr x) = 0; - virtual T operator()(Custom_Warning_Ptr x) = 0; - virtual T operator()(Custom_Error_Ptr x) = 0; - virtual T operator()(Variable_Ptr x) = 0; - virtual T operator()(Number_Ptr x) = 0; - virtual T operator()(Color_Ptr x) = 0; - virtual T operator()(Boolean_Ptr x) = 0; - virtual T operator()(String_Schema_Ptr x) = 0; - virtual T operator()(String_Quoted_Ptr x) = 0; - virtual T operator()(String_Constant_Ptr x) = 0; - virtual T operator()(Supports_Condition_Ptr x) = 0; - virtual T operator()(Supports_Operator_Ptr x) = 0; - virtual T operator()(Supports_Negation_Ptr x) = 0; - virtual T operator()(Supports_Declaration_Ptr x) = 0; - virtual T operator()(Supports_Interpolation_Ptr x) = 0; - virtual T operator()(Media_Query_Ptr x) = 0; - virtual T operator()(Media_Query_Expression_Ptr x) = 0; - virtual T operator()(At_Root_Query_Ptr x) = 0; - virtual T operator()(Null_Ptr x) = 0; - virtual T operator()(Parent_Selector_Ptr x) = 0; + 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()(Supports_Condition* x) = 0; + virtual T operator()(Supports_Operator* x) = 0; + virtual T operator()(Supports_Negation* x) = 0; + virtual T operator()(Supports_Declaration* x) = 0; + virtual T operator()(Supports_Interpolation* x) = 0; + virtual T operator()(Media_Query* x) = 0; + virtual T operator()(Media_Query_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_Ptr x) = 0; - virtual T operator()(Parameters_Ptr x) = 0; - virtual T operator()(Argument_Ptr x) = 0; - virtual T operator()(Arguments_Ptr x) = 0; + 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_Ptr x) = 0; - virtual T operator()(Placeholder_Selector_Ptr x) = 0; - virtual T operator()(Element_Selector_Ptr x) = 0; - virtual T operator()(Class_Selector_Ptr x) = 0; - virtual T operator()(Id_Selector_Ptr x) = 0; - virtual T operator()(Attribute_Selector_Ptr x) = 0; - virtual T operator()(Pseudo_Selector_Ptr x) = 0; - virtual T operator()(Wrapped_Selector_Ptr x) = 0; - virtual T operator()(Compound_Selector_Ptr x)= 0; - virtual T operator()(Complex_Selector_Ptr x) = 0; - virtual T operator()(Selector_List_Ptr x) = 0; + virtual T operator()(Selector_Schema* x) = 0; + virtual T operator()(Placeholder_Selector* x) = 0; + virtual T operator()(Type_Selector* x) = 0; + virtual T operator()(Class_Selector* x) = 0; + virtual T operator()(Id_Selector* x) = 0; + virtual T operator()(Attribute_Selector* x) = 0; + virtual T operator()(Pseudo_Selector* 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; - template - T fallback(U x) { return T(); } }; + // 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: - D& impl() { return static_cast(*this); } - public: - T operator()(AST_Node_Ptr x) { return static_cast(this)->fallback(x); } + T operator()(AST_Node* x) { return static_cast(this)->fallback(x); } // statements - T operator()(Block_Ptr x) { return static_cast(this)->fallback(x); } - T operator()(Ruleset_Ptr x) { return static_cast(this)->fallback(x); } - T operator()(Bubble_Ptr x) { return static_cast(this)->fallback(x); } - T operator()(Trace_Ptr x) { return static_cast(this)->fallback(x); } - T operator()(Supports_Block_Ptr x) { return static_cast(this)->fallback(x); } - T operator()(Media_Block_Ptr x) { return static_cast(this)->fallback(x); } - T operator()(At_Root_Block_Ptr x) { return static_cast(this)->fallback(x); } - T operator()(Directive_Ptr x) { return static_cast(this)->fallback(x); } - T operator()(Keyframe_Rule_Ptr x) { return static_cast(this)->fallback(x); } - T operator()(Declaration_Ptr x) { return static_cast(this)->fallback(x); } - T operator()(Assignment_Ptr x) { return static_cast(this)->fallback(x); } - T operator()(Import_Ptr x) { return static_cast(this)->fallback(x); } - T operator()(Import_Stub_Ptr x) { return static_cast(this)->fallback(x); } - T operator()(Warning_Ptr x) { return static_cast(this)->fallback(x); } - T operator()(Error_Ptr x) { return static_cast(this)->fallback(x); } - T operator()(Debug_Ptr x) { return static_cast(this)->fallback(x); } - T operator()(Comment_Ptr x) { return static_cast(this)->fallback(x); } - T operator()(If_Ptr x) { return static_cast(this)->fallback(x); } - T operator()(For_Ptr x) { return static_cast(this)->fallback(x); } - T operator()(Each_Ptr x) { return static_cast(this)->fallback(x); } - T operator()(While_Ptr x) { return static_cast(this)->fallback(x); } - T operator()(Return_Ptr x) { return static_cast(this)->fallback(x); } - T operator()(Content_Ptr x) { return static_cast(this)->fallback(x); } - T operator()(Extension_Ptr x) { return static_cast(this)->fallback(x); } - T operator()(Definition_Ptr x) { return static_cast(this)->fallback(x); } - T operator()(Mixin_Call_Ptr x) { return static_cast(this)->fallback(x); } + T operator()(Block* x) { return static_cast(this)->fallback(x); } + T operator()(Ruleset* 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()(Supports_Block* x) { return static_cast(this)->fallback(x); } + T operator()(MediaRule* x) { return static_cast(this)->fallback(x); } + T operator()(CssMediaRule* x) { return static_cast(this)->fallback(x); } + T operator()(CssMediaQuery* x) { return static_cast(this)->fallback(x); } + T operator()(At_Root_Block* x) { return static_cast(this)->fallback(x); } + T operator()(Directive* x) { return static_cast(this)->fallback(x); } + T operator()(Keyframe_Rule* x) { return static_cast(this)->fallback(x); } + 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()(Warning* x) { return static_cast(this)->fallback(x); } + T operator()(Error* x) { return static_cast(this)->fallback(x); } + T operator()(Debug* 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()(For* x) { return static_cast(this)->fallback(x); } + T operator()(Each* x) { return static_cast(this)->fallback(x); } + T operator()(While* x) { return static_cast(this)->fallback(x); } + T operator()(Return* x) { return static_cast(this)->fallback(x); } + T operator()(Content* x) { return static_cast(this)->fallback(x); } + T operator()(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()(List_Ptr x) { return static_cast(this)->fallback(x); } - T operator()(Map_Ptr x) { return static_cast(this)->fallback(x); } - T operator()(Function_Ptr x) { return static_cast(this)->fallback(x); } - T operator()(Binary_Expression_Ptr x) { return static_cast(this)->fallback(x); } - T operator()(Unary_Expression_Ptr x) { return static_cast(this)->fallback(x); } - T operator()(Function_Call_Ptr x) { return static_cast(this)->fallback(x); } - T operator()(Function_Call_Schema_Ptr x) { return static_cast(this)->fallback(x); } - T operator()(Custom_Warning_Ptr x) { return static_cast(this)->fallback(x); } - T operator()(Custom_Error_Ptr x) { return static_cast(this)->fallback(x); } - T operator()(Variable_Ptr x) { return static_cast(this)->fallback(x); } - T operator()(Number_Ptr x) { return static_cast(this)->fallback(x); } - T operator()(Color_Ptr x) { return static_cast(this)->fallback(x); } - T operator()(Boolean_Ptr x) { return static_cast(this)->fallback(x); } - T operator()(String_Schema_Ptr x) { return static_cast(this)->fallback(x); } - T operator()(String_Constant_Ptr x) { return static_cast(this)->fallback(x); } - T operator()(String_Quoted_Ptr x) { return static_cast(this)->fallback(x); } - T operator()(Supports_Condition_Ptr x) { return static_cast(this)->fallback(x); } - T operator()(Supports_Operator_Ptr x) { return static_cast(this)->fallback(x); } - T operator()(Supports_Negation_Ptr x) { return static_cast(this)->fallback(x); } - T operator()(Supports_Declaration_Ptr x) { return static_cast(this)->fallback(x); } - T operator()(Supports_Interpolation_Ptr x) { return static_cast(this)->fallback(x); } - T operator()(Media_Query_Ptr x) { return static_cast(this)->fallback(x); } - T operator()(Media_Query_Expression_Ptr x) { return static_cast(this)->fallback(x); } - T operator()(At_Root_Query_Ptr x) { return static_cast(this)->fallback(x); } - T operator()(Null_Ptr x) { return static_cast(this)->fallback(x); } - T operator()(Parent_Selector_Ptr x) { return static_cast(this)->fallback(x); } + 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()(Supports_Condition* x) { return static_cast(this)->fallback(x); } + T operator()(Supports_Operator* x) { return static_cast(this)->fallback(x); } + T operator()(Supports_Negation* x) { return static_cast(this)->fallback(x); } + T operator()(Supports_Declaration* 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_Ptr x) { return static_cast(this)->fallback(x); } - T operator()(Parameters_Ptr x) { return static_cast(this)->fallback(x); } - T operator()(Argument_Ptr x) { return static_cast(this)->fallback(x); } - T operator()(Arguments_Ptr x) { return static_cast(this)->fallback(x); } + 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_Ptr x) { return static_cast(this)->fallback(x); } - T operator()(Placeholder_Selector_Ptr x) { return static_cast(this)->fallback(x); } - T operator()(Element_Selector_Ptr x) { return static_cast(this)->fallback(x); } - T operator()(Class_Selector_Ptr x) { return static_cast(this)->fallback(x); } - T operator()(Id_Selector_Ptr x) { return static_cast(this)->fallback(x); } - T operator()(Attribute_Selector_Ptr x) { return static_cast(this)->fallback(x); } - T operator()(Pseudo_Selector_Ptr x) { return static_cast(this)->fallback(x); } - T operator()(Wrapped_Selector_Ptr x) { return static_cast(this)->fallback(x); } - T operator()(Compound_Selector_Ptr x){ return static_cast(this)->fallback(x); } - T operator()(Complex_Selector_Ptr x) { return static_cast(this)->fallback(x); } - T operator()(Selector_List_Ptr x) { return static_cast(this)->fallback(x); } + T operator()(Selector_Schema* x) { return static_cast(this)->fallback(x); } + T operator()(Placeholder_Selector* x) { return static_cast(this)->fallback(x); } + T operator()(Type_Selector* x) { return static_cast(this)->fallback(x); } + T operator()(Class_Selector* x) { return static_cast(this)->fallback(x); } + T operator()(Id_Selector* x) { return static_cast(this)->fallback(x); } + T operator()(Attribute_Selector* x) { return static_cast(this)->fallback(x); } + T operator()(Pseudo_Selector* x) { return static_cast(this)->fallback(x); } + T operator()(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()); + } - template - T fallback(U x) { return T(); } }; } diff --git a/src/libsass/src/operators.cpp b/src/libsass/src/operators.cpp index a1fd56235..b09293988 100644 --- a/src/libsass/src/operators.cpp +++ b/src/libsass/src/operators.cpp @@ -1,4 +1,8 @@ +// sass.hpp must go before all system headers to get the +// __EXTENSIONS__ fix on Solaris. #include "sass.hpp" + +#include #include "operators.hpp" namespace Sass { @@ -57,26 +61,21 @@ namespace Sass { /* colour math deprecation warning */ void op_color_deprecation(enum Sass_OP op, std::string lsh, std::string rhs, const ParserState& pstate) { - std::string op_str( - op == Sass_OP::ADD ? "plus" : - op == Sass_OP::DIV ? "div" : - op == Sass_OP::SUB ? "minus" : - op == Sass_OP::MUL ? "times" : "" - ); - - std::string msg("The operation `" + lsh + " " + op_str + " " + rhs + "` is deprecated and will be an error in future versions."); - std::string tail("Consider using Sass's color functions instead.\nhttp://sass-lang.com/documentation/Sass/Script/Functions.html#other_color_functions"); - - deprecated(msg, tail, false, 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_Ptr op_strings(Sass::Operand operand, Value& lhs, Value& rhs, struct Sass_Inspect_Options opt, const ParserState& pstate, bool delayed) + Value* op_strings(Sass::Operand operand, Value& lhs, Value& rhs, struct Sass_Inspect_Options opt, const ParserState& pstate, bool delayed) { enum Sass_OP op = operand.operand; - String_Quoted_Ptr lqstr = Cast(&lhs); - String_Quoted_Ptr rqstr = Cast(&rhs); + String_Quoted* lqstr = Cast(&lhs); + String_Quoted* rqstr = Cast(&rhs); std::string lstr(lqstr ? lqstr->value() : lhs.to_string(opt)); std::string rstr(rqstr ? rqstr->value() : rhs.to_string(opt)); @@ -120,8 +119,9 @@ namespace Sass { 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_Ptr op_colors(enum Sass_OP op, const Color& lhs, const Color& rhs, struct Sass_Inspect_Options opt, const ParserState& pstate, bool delayed) + Value* op_colors(enum Sass_OP op, const Color_RGBA& lhs, const Color_RGBA& rhs, struct Sass_Inspect_Options opt, const ParserState& pstate, bool delayed) { if (lhs.a() != rhs.a()) { @@ -133,7 +133,7 @@ namespace Sass { op_color_deprecation(op, lhs.to_string(), rhs.to_string(), pstate); - return SASS_MEMORY_NEW(Color, + return SASS_MEMORY_NEW(Color_RGBA, pstate, ops[op](lhs.r(), rhs.r()), ops[op](lhs.g(), rhs.g()), @@ -142,7 +142,7 @@ namespace Sass { } /* static function, throws OperationError, has no traces but optional pstate for returned value */ - Value_Ptr op_numbers(enum Sass_OP op, const Number& lhs, const Number& rhs, struct Sass_Inspect_Options opt, const ParserState& pstate, bool delayed) + Value* op_numbers(enum Sass_OP op, const Number& lhs, const Number& rhs, struct Sass_Inspect_Options opt, const ParserState& pstate, bool delayed) { double lval = lhs.value(); double rval = rhs.value(); @@ -165,7 +165,7 @@ namespace Sass { 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_Ptr v = SASS_MEMORY_COPY(&lhs); + Number* v = SASS_MEMORY_COPY(&lhs); v->value(ops[op](lval, rval)); return v; } @@ -212,7 +212,7 @@ namespace Sass { } /* static function, throws OperationError, has no traces but optional pstate for returned value */ - Value_Ptr op_number_color(enum Sass_OP op, const Number& lhs, const Color& rhs, struct Sass_Inspect_Options opt, const ParserState& pstate, bool delayed) + Value* op_number_color(enum Sass_OP op, const Number& lhs, const Color_RGBA& rhs, struct Sass_Inspect_Options opt, const ParserState& pstate, bool delayed) { double lval = lhs.value(); @@ -220,7 +220,7 @@ namespace Sass { 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, + return SASS_MEMORY_NEW(Color_RGBA, pstate, ops[op](lval, rhs.r()), ops[op](lval, rhs.g()), @@ -243,7 +243,7 @@ namespace Sass { } /* static function, throws OperationError, has no traces but optional pstate for returned value */ - Value_Ptr op_color_number(enum Sass_OP op, const Color& lhs, const Number& rhs, struct Sass_Inspect_Options opt, const ParserState& pstate, bool delayed) + Value* op_color_number(enum Sass_OP op, const Color_RGBA& lhs, const Number& rhs, struct Sass_Inspect_Options opt, const ParserState& pstate, bool delayed) { double rval = rhs.value(); @@ -254,7 +254,7 @@ namespace Sass { op_color_deprecation(op, lhs.to_string(), rhs.to_string(), pstate); - return SASS_MEMORY_NEW(Color, + return SASS_MEMORY_NEW(Color_RGBA, pstate, ops[op](lhs.r(), rval), ops[op](lhs.g(), rval), diff --git a/src/libsass/src/operators.hpp b/src/libsass/src/operators.hpp index f89eb4ee2..edf09a107 100644 --- a/src/libsass/src/operators.hpp +++ b/src/libsass/src/operators.hpp @@ -17,11 +17,11 @@ namespace Sass { bool lte(Expression_Obj, Expression_Obj); bool gte(Expression_Obj, Expression_Obj); // arithmetic for all the combinations that matter - Value_Ptr op_strings(Sass::Operand, Value&, Value&, struct Sass_Inspect_Options opt, const ParserState& pstate, bool delayed = false); - Value_Ptr op_colors(enum Sass_OP, const Color&, const Color&, struct Sass_Inspect_Options opt, const ParserState& pstate, bool delayed = false); - Value_Ptr op_numbers(enum Sass_OP, const Number&, const Number&, struct Sass_Inspect_Options opt, const ParserState& pstate, bool delayed = false); - Value_Ptr op_number_color(enum Sass_OP, const Number&, const Color&, struct Sass_Inspect_Options opt, const ParserState& pstate, bool delayed = false); - Value_Ptr op_color_number(enum Sass_OP, const Color&, const Number&, struct Sass_Inspect_Options opt, const ParserState& pstate, bool delayed = false); + Value* op_strings(Sass::Operand, Value&, Value&, struct Sass_Inspect_Options opt, const ParserState& pstate, bool delayed = false); + Value* op_colors(enum Sass_OP, const Color_RGBA&, const Color_RGBA&, struct Sass_Inspect_Options opt, const ParserState& pstate, bool delayed = false); + Value* op_numbers(enum Sass_OP, const Number&, const Number&, struct Sass_Inspect_Options opt, const ParserState& pstate, bool delayed = false); + Value* op_number_color(enum Sass_OP, const Number&, const Color_RGBA&, struct Sass_Inspect_Options opt, const ParserState& pstate, bool delayed = false); + Value* op_color_number(enum Sass_OP, const Color_RGBA&, const Number&, struct Sass_Inspect_Options opt, const ParserState& pstate, bool delayed = false); }; diff --git a/src/libsass/src/ordered_map.hpp b/src/libsass/src/ordered_map.hpp new file mode 100644 index 000000000..faa55d343 --- /dev/null +++ b/src/libsass/src/ordered_map.hpp @@ -0,0 +1,112 @@ +#ifndef SASS_ORDERED_MAP_H +#define SASS_ORDERED_MAP_H + +namespace Sass { + + // ########################################################################## + // Very simple and limited container for insert ordered hash map. + // Piggy-back implementation on std::unordered_map and std::vector + // ########################################################################## + template< + class Key, + class T, + class Hash = std::hash, + class KeyEqual = std::equal_to, + class Allocator = std::allocator> + > + class ordered_map { + + private: + + using map_type = typename std::unordered_map< Key, T, Hash, KeyEqual, Allocator>; + using map_iterator = typename std::unordered_map< Key, T, Hash, KeyEqual, Allocator>::iterator; + using map_const_iterator = typename std::unordered_map< Key, T, Hash, KeyEqual, Allocator>::const_iterator; + + // The main unordered map + map_type _map; + + // Keep insertion order + std::vector _keys; + std::vector _values; + + const KeyEqual _keyEqual; + + public: + + ordered_map() : + _keyEqual(KeyEqual()) + { + } + + ordered_map& operator= (const ordered_map& other) { + _map = other._map; + _keys = other._keys; + _values = other._values; + return *this; + } + + std::pair front() { + return std::make_pair( + _keys.front(), + _values.front() + ); + } + + bool empty() const { + return _keys.empty(); + } + + void insert(const Key& key, const T& val) { + if (!hasKey(key)) { + _values.push_back(val); + _keys.push_back(key); + } + _map[key] = val; + } + + bool hasKey(const Key& key) const { + return _map.find(key) != _map.end(); + } + + bool erase(const Key& key) { + _map.erase(key); + // find the position in the array + for (size_t i = 0; i < _keys.size(); i += 1) { + if (_keyEqual(key, _keys[i])) { + _keys.erase(_keys.begin() + i); + _values.erase(_values.begin() + i); + return true; + } + } + return false; + } + + const std::vector& keys() const { return _keys; } + const std::vector& values() const { return _values; } + + const T& get(const Key& key) { + if (hasKey(key)) { + return _map[key]; + } + throw std::runtime_error("Key does not exist"); + } + + using iterator = typename std::vector::iterator; + using const_iterator = typename std::vector::const_iterator; + using reverse_iterator = typename std::vector::reverse_iterator; + using const_reverse_iterator = typename std::vector::const_reverse_iterator; + + typename std::vector::iterator end() { return _keys.end(); } + typename std::vector::iterator begin() { return _keys.begin(); } + typename std::vector::reverse_iterator rend() { return _keys.rend(); } + typename std::vector::reverse_iterator rbegin() { return _keys.rbegin(); } + typename std::vector::const_iterator end() const { return _keys.end(); } + typename std::vector::const_iterator begin() const { return _keys.begin(); } + typename std::vector::const_reverse_iterator rend() const { return _keys.rend(); } + typename std::vector::const_reverse_iterator rbegin() const { return _keys.rbegin(); } + + }; + +} + +#endif diff --git a/src/libsass/src/output.cpp b/src/libsass/src/output.cpp index b2ca65e7e..272dc3ae2 100644 --- a/src/libsass/src/output.cpp +++ b/src/libsass/src/output.cpp @@ -1,6 +1,10 @@ +// 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" namespace Sass { @@ -12,12 +16,12 @@ namespace Sass { Output::~Output() { } - void Output::fallback_impl(AST_Node_Ptr n) + void Output::fallback_impl(AST_Node* n) { return n->perform(this); } - void Output::operator()(Number_Ptr n) + void Output::operator()(Number* n) { // check for a valid unit here // includes result for reporting @@ -31,12 +35,12 @@ namespace Sass { append_token(res, n); } - void Output::operator()(Import_Ptr imp) + void Output::operator()(Import* imp) { top_nodes.push_back(imp); } - void Output::operator()(Map_Ptr m) + void Output::operator()(Map* m) { // should be handle in check_expression throw Exception::InvalidValue({}, *m); @@ -86,9 +90,8 @@ namespace Sass { } - void Output::operator()(Comment_Ptr c) + void Output::operator()(Comment* c) { - std::string txt = c->text()->to_string(opt); // if (indentation && txt == "/**/") return; bool important = c->is_important(); if (output_style() != COMPRESSED || important) { @@ -108,15 +111,17 @@ namespace Sass { } } - void Output::operator()(Ruleset_Ptr r) + void Output::operator()(Ruleset* r) { - Selector_Obj s = r->selector(); - Block_Obj b = r->block(); + Block_Obj b = r->block(); + SelectorListObj s = r->selector(); + + if (!s || s->empty()) return; // Filter out rulesets that aren't printable (process its children though) if (!Util::isPrintable(r, output_style())) { for (size_t i = 0, L = b->length(); i < L; ++i) { - const Statement_Obj& stm = b->at(i); + const Statement_Obj& stm = b->get(i); if (Cast(stm)) { if (!Cast(stm)) { stm->perform(this); @@ -126,7 +131,9 @@ namespace Sass { return; } - if (output_style() == NESTED) indentation += r->tabs(); + if (output_style() == NESTED) { + indentation += r->tabs(); + } if (opt.source_comments) { std::stringstream ss; append_indentation(); @@ -139,22 +146,22 @@ namespace Sass { if (s) s->perform(this); append_scope_opener(b); for (size_t i = 0, L = b->length(); i < L; ++i) { - Statement_Obj stm = b->at(i); + Statement_Obj stm = b->get(i); bool bPrintExpression = true; // Check print conditions - if (Declaration_Ptr dec = Cast(stm)) { - if (String_Constant_Ptr valConst = Cast(dec->value())) { - std::string val(valConst->value()); - if (String_Quoted_Ptr qstr = Cast(valConst)) { + if (Declaration* dec = Cast(stm)) { + if (const String_Constant* valConst = Cast(dec->value())) { + const std::string& val = valConst->value(); + if (const String_Quoted* qstr = Cast(valConst)) { if (!qstr->quote_mark() && val.empty()) { bPrintExpression = false; } } } - else if (List_Ptr list = Cast(dec->value())) { + 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_Ptr item = list->at(list_i); + Expression* item = list->get(list_i); if (!item->is_invisible()) all_invisible = false; } if (all_invisible && !list->is_bracketed()) bPrintExpression = false; @@ -169,7 +176,7 @@ namespace Sass { append_scope_closer(b); } - void Output::operator()(Keyframe_Rule_Ptr r) + void Output::operator()(Keyframe_Rule* r) { Block_Obj b = r->block(); Selector_Obj v = r->name(); @@ -185,14 +192,14 @@ namespace Sass { append_scope_opener(); for (size_t i = 0, L = b->length(); i < L; ++i) { - Statement_Obj stm = b->at(i); + Statement_Obj stm = b->get(i); stm->perform(this); if (i < L - 1) append_special_linefeed(); } append_scope_closer(); } - void Output::operator()(Supports_Block_Ptr f) + void Output::operator()(Supports_Block* f) { if (f->is_invisible()) return; @@ -202,7 +209,7 @@ namespace Sass { // Filter out feature blocks that aren't printable (process its children though) if (!Util::isPrintable(f, output_style())) { for (size_t i = 0, L = b->length(); i < L; ++i) { - Statement_Obj stm = b->at(i); + Statement_Obj stm = b->get(i); if (Cast(stm)) { stm->perform(this); } @@ -218,7 +225,7 @@ namespace Sass { append_scope_opener(); for (size_t i = 0, L = b->length(); i < L; ++i) { - Statement_Obj stm = b->at(i); + Statement_Obj stm = b->get(i); stm->perform(this); if (i < L - 1) append_special_linefeed(); } @@ -229,44 +236,24 @@ namespace Sass { } - void Output::operator()(Media_Block_Ptr m) + void Output::operator()(CssMediaRule* rule) { - if (m->is_invisible()) return; - - Block_Obj b = m->block(); - - // Filter out media blocks that aren't printable (process its children though) - if (!Util::isPrintable(m, output_style())) { - for (size_t i = 0, L = b->length(); i < L; ++i) { - Statement_Obj stm = b->at(i); - if (Cast(stm)) { - stm->perform(this); - } - } - return; + // Avoid null pointer exception + if (rule == nullptr) return; + // Skip empty/invisible rule + if (rule->isInvisible()) return; + // Avoid null pointer exception + if (rule->block() == nullptr) return; + // Skip empty/invisible rule + if (rule->block()->isInvisible()) return; + // Skip if block is empty/invisible + if (Util::isPrintable(rule, output_style())) { + // Let inspect do its magic + Inspect::operator()(rule); } - if (output_style() == NESTED) indentation += m->tabs(); - append_indentation(); - append_token("@media", m); - append_mandatory_space(); - in_media_block = true; - m->media_queries()->perform(this); - in_media_block = false; - append_scope_opener(); - - for (size_t i = 0, L = b->length(); i < L; ++i) { - if (b->at(i)) { - Statement_Obj stm = b->at(i); - stm->perform(this); - } - if (i < L - 1) append_special_linefeed(); - } - - if (output_style() == NESTED) indentation -= m->tabs(); - append_scope_closer(); } - void Output::operator()(Directive_Ptr a) + void Output::operator()(Directive* a) { std::string kwd = a->keyword(); Selector_Obj s = a->selector(); @@ -301,7 +288,7 @@ namespace Sass { bool format = kwd != "@font-face";; for (size_t i = 0, L = b->length(); i < L; ++i) { - Statement_Obj stm = b->at(i); + Statement_Obj stm = b->get(i); stm->perform(this); if (i < L - 1 && format) append_special_linefeed(); } @@ -309,7 +296,7 @@ namespace Sass { append_scope_closer(); } - void Output::operator()(String_Quoted_Ptr s) + void Output::operator()(String_Quoted* s) { if (s->quote_mark()) { append_token(quote(s->value(), s->quote_mark()), s); @@ -320,12 +307,9 @@ namespace Sass { } } - void Output::operator()(String_Constant_Ptr s) + void Output::operator()(String_Constant* s) { std::string value(s->value()); - if (s->can_compress_whitespace() && output_style() == COMPRESSED) { - value.erase(std::remove_if(value.begin(), value.end(), ::isspace), value.end()); - } if (!in_comment && !in_custom_property) { append_token(string_to_output(value), s); } else { diff --git a/src/libsass/src/output.hpp b/src/libsass/src/output.hpp index c460b13fe..4d0e1bcf7 100644 --- a/src/libsass/src/output.hpp +++ b/src/libsass/src/output.hpp @@ -11,13 +11,6 @@ namespace Sass { class Context; - // Refactor to make it generic to find linefeed (look behind) - inline bool ends_with(std::string const & value, std::string const & ending) - { - if (ending.size() > value.size()) return false; - return std::equal(ending.rbegin(), ending.rend(), value.rbegin()); - } - class Output : public Inspect { protected: using Inspect::operator(); @@ -28,24 +21,24 @@ namespace Sass { protected: std::string charset; - std::vector top_nodes; + std::vector top_nodes; public: OutputBuffer get_buffer(void); - virtual void operator()(Map_Ptr); - virtual void operator()(Ruleset_Ptr); - virtual void operator()(Supports_Block_Ptr); - virtual void operator()(Media_Block_Ptr); - virtual void operator()(Directive_Ptr); - virtual void operator()(Keyframe_Rule_Ptr); - virtual void operator()(Import_Ptr); - virtual void operator()(Comment_Ptr); - virtual void operator()(Number_Ptr); - virtual void operator()(String_Quoted_Ptr); - virtual void operator()(String_Constant_Ptr); - - void fallback_impl(AST_Node_Ptr n); + virtual void operator()(Map*); + virtual void operator()(Ruleset*); + virtual void operator()(Supports_Block*); + virtual void operator()(CssMediaRule*); + virtual void operator()(Directive*); + 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); }; diff --git a/src/libsass/src/parser.cpp b/src/libsass/src/parser.cpp index 28fe02244..ce20ba811 100644 --- a/src/libsass/src/parser.cpp +++ b/src/libsass/src/parser.cpp @@ -1,13 +1,10 @@ +// sass.hpp must go before all system headers to get the +// __EXTENSIONS__ fix on Solaris. #include "sass.hpp" + #include "parser.hpp" -#include "file.hpp" -#include "inspect.hpp" -#include "constants.hpp" -#include "util.hpp" -#include "prelexer.hpp" #include "color_maps.hpp" -#include "sass/functions.h" -#include "error_handling.hpp" +#include "util_string.hpp" // Notes about delayed: some ast nodes can have delayed evaluation so // they can preserve their original semantics if needed. This is most @@ -21,20 +18,16 @@ // Another case with delayed values are colors. In compressed mode // only processed values get compressed (other are left as written). -#include -#include -#include -#include namespace Sass { using namespace Constants; using namespace Prelexer; - Parser Parser::from_c_str(const char* beg, Context& ctx, Backtraces traces, ParserState pstate, const char* source) + Parser Parser::from_c_str(const char* beg, Context& ctx, Backtraces traces, ParserState pstate, const char* source, bool allow_parent) { pstate.offset.column = 0; pstate.offset.line = 0; - Parser p(ctx, pstate, traces); + Parser p(ctx, pstate, traces, allow_parent); p.source = source ? source : beg; p.position = beg ? beg : p.source; p.end = p.position + strlen(p.position); @@ -44,11 +37,11 @@ namespace Sass { return p; } - Parser Parser::from_c_str(const char* beg, const char* end, Context& ctx, Backtraces traces, ParserState pstate, const char* source) + Parser Parser::from_c_str(const char* beg, const char* end, Context& ctx, Backtraces traces, ParserState pstate, const char* source, bool allow_parent) { pstate.offset.column = 0; pstate.offset.line = 0; - Parser p(ctx, pstate, traces); + Parser p(ctx, pstate, traces, allow_parent); p.source = source ? source : beg; p.position = beg ? beg : p.source; p.end = end ? end : p.position + strlen(p.position); @@ -66,12 +59,11 @@ namespace Sass { pstate.offset.line = 0; } - Selector_List_Obj Parser::parse_selector(const char* beg, Context& ctx, Backtraces traces, ParserState pstate, const char* source) + SelectorListObj Parser::parse_selector(const char* beg, Context& ctx, Backtraces traces, ParserState pstate, const char* source, bool allow_parent) { - Parser p = Parser::from_c_str(beg, ctx, traces, pstate, source); - // ToDo: ruby sass errors on parent references + Parser p = Parser::from_c_str(beg, ctx, traces, pstate, source, allow_parent); // ToDo: remap the source-map entries somehow - return p.parse_selector_list(false); + return p.parseSelectorList(false); } bool Parser::peek_newline(const char* start) @@ -258,16 +250,23 @@ namespace Sass { else if (lex < kwd_extend >(true)) { Lookahead lookahead = lookahead_for_include(position); if (!lookahead.found) css_error("Invalid CSS", " after ", ": expected selector, was "); - Selector_List_Obj target; + SelectorListObj target; if (!lookahead.has_interpolants) { - target = parse_selector_list(true); + LOCAL_FLAG(allow_parent, false); + auto selector = parseSelectorList(true); + auto extender = SASS_MEMORY_NEW(ExtendRule, pstate, selector); + extender->isOptional(selector && selector->is_optional()); + block->append(extender); } else { - target = SASS_MEMORY_NEW(Selector_List, pstate); - target->schema(parse_selector_schema(lookahead.found, true)); + LOCAL_FLAG(allow_parent, false); + auto selector = parse_selector_schema(lookahead.found, true); + auto extender = SASS_MEMORY_NEW(ExtendRule, pstate, selector); + // A schema is not optional yet, check once it is evaluated + // extender->isOptional(selector && selector->is_optional()); + block->append(extender); } - block->append(SASS_MEMORY_NEW(Extension, pstate, target)); } // selector may contain interpolations which need delayed evaluation @@ -280,7 +279,7 @@ namespace Sass { } // parse multiple specific keyword directives - else if (lex < kwd_media >(true)) { block->append(parse_media_block()); } + else if (lex < kwd_media >(true)) { block->append(parseMediaRule()); } else if (lex < kwd_at_root >(true)) { block->append(parse_at_root_block()); } else if (lex < kwd_include_directive >(true)) { block->append(parse_include_directive()); } else if (lex < kwd_content_directive >(true)) { block->append(parse_content_directive()); } @@ -291,9 +290,9 @@ namespace Sass { // ignore the @charset directive for now else if (lex< kwd_charset_directive >(true)) { parse_charset_directive(); } + else if (lex < exactly < else_kwd >>(true)) { error("Invalid CSS: @else must come after @if"); } + // generic at keyword (keep last) - else if (lex< re_special_directive >(true)) { block->append(parse_special_directive()); } - else if (lex< re_prefixed_directive >(true)) { block->append(parse_prefixed_directive()); } else if (lex< at_keyword >(true)) { block->append(parse_directive()); } else if (is_root && stack.back() != Scope::AtRoot /* && block->is_root() */) { @@ -333,11 +332,11 @@ namespace Sass { do { while (lex< block_comment >()); if (lex< quoted_string >()) { - to_import.push_back(std::pair(std::string(lexed), 0)); + to_import.push_back(std::pair(std::string(lexed), {})); } else if (lex< uri_prefix >()) { Arguments_Obj args = SASS_MEMORY_NEW(Arguments, pstate); - Function_Call_Obj result = SASS_MEMORY_NEW(Function_Call, pstate, "url", args); + Function_Call_Obj result = SASS_MEMORY_NEW(Function_Call, pstate, std::string("url"), args); if (lex< quoted_string >()) { Expression_Obj quoted_url = parse_string(); @@ -479,7 +478,7 @@ namespace Sass { bool is_arglist = false; bool is_keyword = false; Expression_Obj val = parse_space_list(); - List_Ptr l = Cast(val); + List* l = Cast(val); if (lex_css< exactly< ellipsis > >()) { if (val->concrete_type() == Expression::MAP || ( (l != NULL && l->separator() == SASS_HASH) @@ -527,10 +526,13 @@ namespace Sass { // create the connector object (add parts later) Ruleset_Obj ruleset = SASS_MEMORY_NEW(Ruleset, pstate); // parse selector static or as schema to be evaluated later - if (lookahead.parsable) ruleset->selector(parse_selector_list(false)); + if (lookahead.parsable) { + ruleset->selector(parseSelectorList(false)); + } else { - Selector_List_Obj list = SASS_MEMORY_NEW(Selector_List, pstate); - list->schema(parse_selector_schema(lookahead.position, false)); + SelectorListObj list = SASS_MEMORY_NEW(SelectorList, pstate); + auto sc = parse_selector_schema(lookahead.position, false); + ruleset->schema(sc); ruleset->selector(list); } // then parse the inner block @@ -556,15 +558,14 @@ namespace Sass { lex< optional_spaces >(); const char* i = position; // selector schema re-uses string schema implementation - String_Schema_Ptr schema = SASS_MEMORY_NEW(String_Schema, pstate); + 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); - selector_schema->media_block(last_media_block); // process until end while (i < end_of_selector) { - // try to parse mutliple interpolants + // 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) { @@ -645,224 +646,34 @@ namespace Sass { // normalize underscores to hyphens std::string name(Util::normalize_underscores(lexed)); // create the initial mixin call object - Mixin_Call_Obj call = SASS_MEMORY_NEW(Mixin_Call, pstate, name, 0, 0); + Mixin_Call_Obj call = SASS_MEMORY_NEW(Mixin_Call, pstate, name, {}, {}, {}); // 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 (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 - // parse a list of complex selectors - // this is the main entry point for most - Selector_List_Obj Parser::parse_selector_list(bool chroot) - { - bool reloop; - bool had_linefeed = false; - NESTING_GUARD(nestings); - Complex_Selector_Obj sel; - Selector_List_Obj group = SASS_MEMORY_NEW(Selector_List, pstate); - group->media_block(last_media_block); - - if (peek_css< alternatives < end_of_file, exactly <'{'>, exactly <','> > >()) { - css_error("Invalid CSS", " after ", ": expected selector, was "); - } - - do { - reloop = false; - - had_linefeed = had_linefeed || peek_newline(); - - if (peek_css< alternatives < class_char < selector_list_delims > > >()) - break; // in case there are superfluous commas at the end - - // now parse the complex selector - sel = parse_complex_selector(chroot); - - if (!sel) return group.detach(); - - sel->has_line_feed(had_linefeed); - - had_linefeed = false; - - while (peek_css< exactly<','> >()) - { - lex< css_comments >(false); - // consume everything up and including the comma separator - reloop = lex< exactly<','> >() != 0; - // remember line break (also between some commas) - had_linefeed = had_linefeed || peek_newline(); - // remember line break (also between some commas) - } - group->append(sel); - } - while (reloop); - while (lex_css< kwd_optional >()) { - group->is_optional(true); - } - // update for end position - group->update_pstate(pstate); - if (sel) sel->last()->has_line_break(false); - return group.detach(); - } - // EO parse_selector_list - - // a complex selector combines a compound selector with another - // complex selector, with one of four combinator operations. - // the compound selector (head) is optional, since the combinator - // can come first in the whole selector sequence (like `> DIV'). - Complex_Selector_Obj Parser::parse_complex_selector(bool chroot) - { - - NESTING_GUARD(nestings); - String_Obj reference = 0; - lex < block_comment >(); - advanceToNextToken(); - Complex_Selector_Obj sel = SASS_MEMORY_NEW(Complex_Selector, pstate); - - if (peek < end_of_file >()) return 0; - - // parse the left hand side - Compound_Selector_Obj lhs; - // special case if it starts with combinator ([+~>]) - if (!peek_css< class_char < selector_combinator_ops > >()) { - // parse the left hand side - lhs = parse_compound_selector(); - } - - - // parse combinator between lhs and rhs - Complex_Selector::Combinator combinator = Complex_Selector::ANCESTOR_OF; - if (lex< exactly<'+'> >()) combinator = Complex_Selector::ADJACENT_TO; - else if (lex< exactly<'~'> >()) combinator = Complex_Selector::PRECEDES; - else if (lex< exactly<'>'> >()) combinator = Complex_Selector::PARENT_OF; - else if (lex< sequence < exactly<'/'>, negate < exactly < '*' > > > >()) { - // comments are allowed, but not spaces? - combinator = Complex_Selector::REFERENCE; - if (!lex < re_reference_combinator >()) return 0; - reference = SASS_MEMORY_NEW(String_Constant, pstate, lexed); - if (!lex < exactly < '/' > >()) return 0; // ToDo: error msg? - } - - if (!lhs && combinator == Complex_Selector::ANCESTOR_OF) return 0; - - // lex < block_comment >(); - sel->head(lhs); - sel->combinator(combinator); - sel->media_block(last_media_block); - - if (combinator == Complex_Selector::REFERENCE) sel->reference(reference); - // has linfeed after combinator? - sel->has_line_break(peek_newline()); - // sel->has_line_feed(has_line_feed); - - // check if we got the abort condition (ToDo: optimize) - if (!peek_css< class_char < complex_selector_delims > >()) { - // parse next selector in sequence - sel->tail(parse_complex_selector(true)); - } - - // add a parent selector if we are not in a root - // also skip adding parent ref if we only have refs - if (!sel->has_parent_ref() && !chroot) { - // create the objects to wrap parent selector reference - Compound_Selector_Obj head = SASS_MEMORY_NEW(Compound_Selector, pstate); - Parent_Selector_Ptr parent = SASS_MEMORY_NEW(Parent_Selector, pstate, false); - parent->media_block(last_media_block); - head->media_block(last_media_block); - // add simple selector - head->append(parent); - // selector may not have any head yet - if (!sel->head()) { sel->head(head); } - // otherwise we need to create a new complex selector and set the old one as its tail - else { - sel = SASS_MEMORY_NEW(Complex_Selector, pstate, Complex_Selector::ANCESTOR_OF, head, sel); - sel->media_block(last_media_block); - } - // peek for linefeed and remember result on head - // if (peek_newline()) head->has_line_break(true); - } - - sel->update_pstate(pstate); - // complex selector - return sel; - } - // EO parse_complex_selector - - // parse one compound selector, which is basically - // a list of simple selectors (directly adjacent) - // lex them exactly (without skipping white-space) - Compound_Selector_Obj Parser::parse_compound_selector() - { - // init an empty compound selector wrapper - Compound_Selector_Obj seq = SASS_MEMORY_NEW(Compound_Selector, pstate); - seq->media_block(last_media_block); - - // skip initial white-space - lex< css_whitespace >(); - - // parse list - while (true) - { - // remove all block comments (don't skip white-space) - lex< delimited_by< slash_star, star_slash, false > >(false); - // parse functional - if (match < re_pseudo_selector >()) - { - seq->append(parse_simple_selector()); - } - // parse parent selector - else if (lex< exactly<'&'> >(false)) - { - // this produces a linefeed!? - seq->has_parent_reference(true); - seq->append(SASS_MEMORY_NEW(Parent_Selector, pstate)); - // parent selector only allowed at start - // upcoming Sass may allow also trailing - if (seq->length() > 1) { - ParserState state(pstate); - Simple_Selector_Obj cur = (*seq)[seq->length()-1]; - Simple_Selector_Obj prev = (*seq)[seq->length()-2]; - std::string sel(prev->to_string({ NESTED, 5 })); - std::string found(cur->to_string({ NESTED, 5 })); - if (lex < identifier >()) { found += std::string(lexed); } - error("Invalid CSS after \"" + sel + "\": expected \"{\", was \"" + found + "\"\n\n" - "\"" + found + "\" may only be used at the beginning of a compound selector.", state); - } - } - // parse type selector - else if (lex< re_type_selector >(false)) - { - seq->append(SASS_MEMORY_NEW(Element_Selector, pstate, lexed)); - } - // peek for abort conditions - else if (peek< spaces >()) break; - else if (peek< end_of_file >()) { break; } - else if (peek_css < class_char < selector_combinator_ops > >()) break; - else if (peek_css < class_char < complex_selector_delims > >()) break; - // otherwise parse another simple selector - else { - Simple_Selector_Obj sel = parse_simple_selector(); - if (!sel) return 0; - seq->append(sel); - } - } - - if (seq && !peek_css>>()) { - seq->has_line_break(peek_newline()); - } - - // EO while true - return seq; - } - // EO parse_compound_selector - - Simple_Selector_Obj Parser::parse_simple_selector() + SimpleSelectorObj Parser::parse_simple_selector() { lex < css_comments >(false); if (lex< class_name >()) { @@ -872,10 +683,10 @@ namespace Sass { return SASS_MEMORY_NEW(Id_Selector, pstate, lexed); } else if (lex< alternatives < variable, number, static_reference_combinator > >()) { - return SASS_MEMORY_NEW(Element_Selector, pstate, lexed); + return SASS_MEMORY_NEW(Type_Selector, pstate, lexed); } else if (peek< pseudo_not >()) { - return parse_negated_selector(); + return parse_negated_selector2(); } else if (peek< re_pseudo_selector >()) { return parse_pseudo_selector(); @@ -887,86 +698,117 @@ namespace Sass { return parse_attribute_selector(); } else if (lex< placeholder >()) { - Placeholder_Selector_Ptr sel = SASS_MEMORY_NEW(Placeholder_Selector, pstate, lexed); - sel->media_block(last_media_block); - return sel; + return SASS_MEMORY_NEW(Placeholder_Selector, pstate, lexed); } else { css_error("Invalid CSS", " after ", ": expected selector, was "); } // failed - return 0; + return {}; } - Wrapped_Selector_Obj Parser::parse_negated_selector() + Pseudo_Selector_Obj Parser::parse_negated_selector2() { lex< pseudo_not >(); std::string name(lexed); ParserState nsource_position = pstate; - Selector_List_Obj negated = parse_selector_list(true); + SelectorListObj negated = parseSelectorList(true); if (!lex< exactly<')'> >()) { error("negated selector is missing ')'"); } name.erase(name.size() - 1); - return SASS_MEMORY_NEW(Wrapped_Selector, nsource_position, name, negated); + + Pseudo_Selector* sel = SASS_MEMORY_NEW(Pseudo_Selector, nsource_position, name.substr(1)); + sel->selector(negated); + return sel; } + // Helper to clean binominal string + bool BothAreSpaces(char lhs, char rhs) { return isspace(lhs) && isspace(rhs); } + // a pseudo selector often starts with one or two colons // it can contain more selectors inside parentheses - Simple_Selector_Obj Parser::parse_pseudo_selector() { - if (lex< sequence< - optional < pseudo_prefix >, - // we keep the space within the name, strange enough - // ToDo: refactor output to schedule the space for it - // or do we really want to keep the real white-space? - sequence< identifier, optional < block_comment >, exactly<'('> > - > >()) - { - - std::string name(lexed); - name.erase(name.size() - 1); - ParserState p = pstate; + SimpleSelectorObj Parser::parse_pseudo_selector() { + + // Lex one or two colon characters + if (lex()) { + std::string colons(lexed); + // Check if it is a pseudo element + bool element = colons.size() == 2; + + if (lex< sequence< + // we keep the space within the name, strange enough + // ToDo: refactor output to schedule the space for it + // or do we really want to keep the real white-space? + sequence< identifier, optional < block_comment >, exactly<'('> > + > >()) + { - // specially parse static stuff - // ToDo: really everything static? - if (peek_css < - sequence < - alternatives < - static_value, - binomial - >, - optional_css_whitespace, - exactly<')'> - > - >() - ) { - lex_css< alternatives < static_value, binomial > >(); - String_Constant_Obj expr = SASS_MEMORY_NEW(String_Constant, pstate, lexed); - if (lex_css< exactly<')'> >()) { - expr->can_compress_whitespace(true); - return SASS_MEMORY_NEW(Pseudo_Selector, p, name, expr); + std::string name(lexed); + name.erase(name.size() - 1); + ParserState p = pstate; + + // specially parse nth-child pseudo selectors + if (lex_css < sequence < binomial, word_boundary >>()) { + std::string parsed(lexed); // always compacting binominals (as dart-sass) + parsed.erase(std::unique(parsed.begin(), parsed.end(), BothAreSpaces), parsed.end()); + String_Constant_Obj arg = SASS_MEMORY_NEW(String_Constant, pstate, parsed); + Pseudo_Selector* pseudo = SASS_MEMORY_NEW(Pseudo_Selector, p, name, element); + if (lex < sequence < css_whitespace, insensitive < of_kwd >>>(false)) { + pseudo->selector(parseSelectorList(true)); + } + pseudo->argument(arg); + if (lex_css< exactly<')'> >()) { + return pseudo; + } } - } - else if (Selector_List_Obj wrapped = parse_selector_list(true)) { - if (wrapped && lex_css< exactly<')'> >()) { - return SASS_MEMORY_NEW(Wrapped_Selector, p, name, wrapped); + else { + if (peek_css< exactly<')'>>() && Util::equalsLiteral("nth-", name.substr(0, 4))) { + css_error("Invalid CSS", " after ", ": expected An+B expression, was "); + } + + std::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<')'> >()) { + Pseudo_Selector* pseudo = SASS_MEMORY_NEW(Pseudo_Selector, p, name, element); + pseudo->selector(wrapped); + return pseudo; + } + } + } else { + String_Schema_Obj arg = parse_css_variable_value(); + Pseudo_Selector* pseudo = SASS_MEMORY_NEW(Pseudo_Selector, p, name, element); + pseudo->argument(arg); + + if (lex_css< exactly<')'> >()) { + return pseudo; + } + } } + } + // EO if pseudo selector - } - // EO if pseudo selector + else if (lex < sequence< optional < pseudo_prefix >, identifier > >()) { + return SASS_MEMORY_NEW(Pseudo_Selector, pstate, lexed, element); + } + else if (lex < pseudo_prefix >()) { + css_error("Invalid CSS", " after ", ": expected pseudoclass or pseudoelement, was "); + } - else if (lex < sequence< optional < pseudo_prefix >, identifier > >()) { - return SASS_MEMORY_NEW(Pseudo_Selector, pstate, lexed); } - else if(lex < pseudo_prefix >()) { - css_error("Invalid CSS", " after ", ": expected pseudoclass or pseudoelement, was "); + else { + lex < identifier >(); // needed for error message? + css_error("Invalid CSS", " after ", ": expected selector, was "); } + css_error("Invalid CSS", " after ", ": expected \")\", was "); // unreachable statement - return 0; + return {}; } const char* Parser::re_attr_sensitive_close(const char* src) @@ -985,11 +827,11 @@ namespace Sass { if (!lex_css< attribute_name >()) error("invalid attribute name in attribute selector"); std::string name(lexed); if (lex_css< re_attr_sensitive_close >()) { - return SASS_MEMORY_NEW(Attribute_Selector, p, name, "", 0, 0); + return SASS_MEMORY_NEW(Attribute_Selector, p, name, "", {}, {}); } else if (lex_css< re_attr_insensitive_close >()) { char modifier = lexed.begin[0]; - return SASS_MEMORY_NEW(Attribute_Selector, p, name, "", 0, modifier); + return SASS_MEMORY_NEW(Attribute_Selector, p, name, "", {}, modifier); } if (!lex_css< alternatives< exact_match, class_match, dash_match, prefix_match, suffix_match, substring_match > >()) { @@ -997,7 +839,7 @@ namespace Sass { } std::string matcher(lexed); - String_Obj value = 0; + String_Obj value; if (lex_css< identifier >()) { value = SASS_MEMORY_NEW(String_Constant, p, lexed); } @@ -1016,11 +858,11 @@ namespace Sass { return SASS_MEMORY_NEW(Attribute_Selector, p, name, matcher, value, modifier); } error("unterminated attribute selector for " + name); - return NULL; // to satisfy compilers (error must not return) + return {}; // to satisfy compilers (error must not return) } /* parse block comment and add to block */ - void Parser::parse_block_comments() + void Parser::parse_block_comments(bool store) { Block_Obj block = block_stack.back(); @@ -1028,7 +870,7 @@ namespace Sass { bool is_important = lexed.begin[2] == '!'; // flag on second param is to skip loosely over comments String_Obj contents = parse_interpolated_chunk(lexed, true, false); - block->append(SASS_MEMORY_NEW(Comment, pstate, contents, is_important)); + if (store) block->append(SASS_MEMORY_NEW(Comment, pstate, contents, is_important)); } } @@ -1072,7 +914,7 @@ namespace Sass { } else { value = parse_list(DELAYED); - if (List_Ptr list = Cast(value)) { + 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 "); } @@ -1086,23 +928,6 @@ namespace Sass { } } - // parse +/- and return false if negative - // this is never hit via spec tests - bool Parser::parse_number_prefix() - { - bool positive = true; - while(true) { - if (lex < block_comment >()) continue; - if (lex < number_prefix >()) continue; - if (lex < exactly < '-' > >()) { - positive = !positive; - continue; - } - break; - } - return positive; - } - Expression_Obj Parser::parse_map() { NESTING_GUARD(nestings); @@ -1375,7 +1200,7 @@ namespace Sass { Expression_Obj lhs = parse_operators(); // if it's a singleton, return it (don't wrap it) if (!(peek_css< exactly<'+'> >(position) || - // condition is a bit misterious, but some combinations should not be counted as operations + // 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)) @@ -1478,7 +1303,7 @@ namespace Sass { } else if (lex< identifier_schema >()) { String_Obj string = parse_identifier_schema(); - if (String_Schema_Ptr schema = Cast(string)) { + if (String_Schema* schema = Cast(string)) { if (lex < exactly < '(' > >()) { schema->append(parse_list()); lex < exactly < ')' > >(); @@ -1493,32 +1318,25 @@ namespace Sass { return parse_function_call(); } else if (lex< exactly<'+'> >()) { - Unary_Expression_Ptr ex = SASS_MEMORY_NEW(Unary_Expression, pstate, Unary_Expression::PLUS, parse_factor()); + 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_Ptr ex = SASS_MEMORY_NEW(Unary_Expression, pstate, Unary_Expression::MINUS, parse_factor()); + 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_Ptr ex = SASS_MEMORY_NEW(Unary_Expression, pstate, Unary_Expression::SLASH, parse_factor()); + 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_Ptr ex = SASS_MEMORY_NEW(Unary_Expression, pstate, Unary_Expression::NOT, parse_factor()); + 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; } - // this whole branch is never hit via spec tests - else if (peek < sequence < one_plus < alternatives < css_whitespace, exactly<'-'>, exactly<'+'> > >, number > >()) { - if (parse_number_prefix()) return parse_value(); // prefix is positive - Unary_Expression_Ptr ex = SASS_MEMORY_NEW(Unary_Expression, pstate, Unary_Expression::MINUS, parse_value()); - if (ex->operand()) ex->is_delayed(ex->operand()->is_delayed()); - return ex; - } else { return parse_value(); } @@ -1533,9 +1351,9 @@ namespace Sass { (L > 2 && parsed.substr(0, 3) == "-0.") ); } - Number_Ptr Parser::lexed_number(const ParserState& pstate, const std::string& parsed) + Number* Parser::lexed_number(const ParserState& pstate, const std::string& parsed) { - Number_Ptr nr = SASS_MEMORY_NEW(Number, + Number* nr = SASS_MEMORY_NEW(Number, pstate, sass_strtod(parsed.c_str()), "", @@ -1545,9 +1363,9 @@ namespace Sass { return nr; } - Number_Ptr Parser::lexed_percentage(const ParserState& pstate, const std::string& parsed) + Number* Parser::lexed_percentage(const ParserState& pstate, const std::string& parsed) { - Number_Ptr nr = SASS_MEMORY_NEW(Number, + Number* nr = SASS_MEMORY_NEW(Number, pstate, sass_strtod(parsed.c_str()), "%", @@ -1557,7 +1375,7 @@ namespace Sass { return nr; } - Number_Ptr Parser::lexed_dimension(const ParserState& pstate, const std::string& parsed) + Number* Parser::lexed_dimension(const ParserState& pstate, const std::string& parsed) { size_t L = parsed.length(); size_t num_pos = parsed.find_first_not_of(" \n\r\t"); @@ -1568,7 +1386,7 @@ namespace Sass { } if (unit_pos == std::string::npos) unit_pos = L; const std::string& num = parsed.substr(num_pos, unit_pos - num_pos); - Number_Ptr nr = SASS_MEMORY_NEW(Number, + Number* nr = SASS_MEMORY_NEW(Number, pstate, sass_strtod(num.c_str()), Token(number(parsed.c_str())), @@ -1578,9 +1396,9 @@ namespace Sass { return nr; } - Value_Ptr Parser::lexed_hex_color(const ParserState& pstate, const std::string& parsed) + Value* Parser::lexed_hex_color(const ParserState& pstate, const std::string& parsed) { - Color_Ptr color = NULL; + Color_RGBA* color = NULL; if (parsed[0] != '#') { return SASS_MEMORY_NEW(String_Quoted, pstate, parsed); } @@ -1590,7 +1408,7 @@ namespace Sass { std::string r(2, parsed[1]); std::string g(2, parsed[2]); std::string b(2, parsed[3]); - color = SASS_MEMORY_NEW(Color, + color = SASS_MEMORY_NEW(Color_RGBA, pstate, static_cast(strtol(r.c_str(), NULL, 16)), static_cast(strtol(g.c_str(), NULL, 16)), @@ -1603,7 +1421,7 @@ namespace Sass { std::string g(2, parsed[2]); std::string b(2, parsed[3]); std::string a(2, parsed[4]); - color = SASS_MEMORY_NEW(Color, + color = SASS_MEMORY_NEW(Color_RGBA, pstate, static_cast(strtol(r.c_str(), NULL, 16)), static_cast(strtol(g.c_str(), NULL, 16)), @@ -1615,7 +1433,7 @@ namespace Sass { std::string r(parsed.substr(1,2)); std::string g(parsed.substr(3,2)); std::string b(parsed.substr(5,2)); - color = SASS_MEMORY_NEW(Color, + color = SASS_MEMORY_NEW(Color_RGBA, pstate, static_cast(strtol(r.c_str(), NULL, 16)), static_cast(strtol(g.c_str(), NULL, 16)), @@ -1628,7 +1446,7 @@ namespace Sass { std::string g(parsed.substr(3,2)); std::string b(parsed.substr(5,2)); std::string a(parsed.substr(7,2)); - color = SASS_MEMORY_NEW(Color, + color = SASS_MEMORY_NEW(Color_RGBA, pstate, static_cast(strtol(r.c_str(), NULL, 16)), static_cast(strtol(g.c_str(), NULL, 16)), @@ -1641,10 +1459,10 @@ namespace Sass { return color; } - Value_Ptr Parser::color_or_string(const std::string& lexed) const + Value* Parser::color_or_string(const std::string& lexed) const { if (auto color = name_to_color(lexed)) { - auto c = SASS_MEMORY_NEW(Color, color); + auto c = SASS_MEMORY_NEW(Color_RGBA, color); c->is_delayed(true); c->pstate(pstate); c->disp(lexed); @@ -1663,7 +1481,7 @@ namespace Sass { 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_Selector, pstate); } + return SASS_MEMORY_NEW(Parent_Reference, pstate); } if (lex< kwd_important >()) { return SASS_MEMORY_NEW(String_Constant, pstate, "!important"); } @@ -1729,7 +1547,7 @@ namespace Sass { css_error("Invalid CSS", " after ", ": expected expression (e.g. 1px, bold), was "); // unreachable statement - return 0; + return {}; } // this parses interpolation inside other strings @@ -1742,7 +1560,7 @@ namespace Sass { find_first_in_interval< exactly, block_comment >(i, chunk.end); if (!p) { - String_Quoted_Ptr str_quoted = SASS_MEMORY_NEW(String_Quoted, pstate, std::string(i, chunk.end), 0, false, false, true, css); + String_Quoted* str_quoted = SASS_MEMORY_NEW(String_Quoted, pstate, std::string(i, chunk.end), 0, false, false, true, css); if (!constant && str_quoted->quote_mark()) str_quoted->quote_mark('*'); return str_quoted; } @@ -1786,71 +1604,63 @@ namespace Sass { return schema.detach(); } - String_Schema_Obj Parser::parse_css_variable_value(bool top_level) + String_Schema_Obj Parser::parse_css_variable_value() { String_Schema_Obj schema = SASS_MEMORY_NEW(String_Schema, pstate); - String_Schema_Obj tok; - if (!(tok = parse_css_variable_value_token(top_level))) { - return NULL; - } - - schema->concat(tok); - while ((tok = parse_css_variable_value_token(top_level))) { - schema->concat(tok); - } - - return schema.detach(); - } - - String_Schema_Obj Parser::parse_css_variable_value_token(bool top_level) - { - String_Schema_Obj schema = SASS_MEMORY_NEW(String_Schema, pstate); - if ( - (top_level && lex< css_variable_top_level_value >(false)) || - (!top_level && lex< css_variable_value >(false)) - ) { - Token str(lexed); - schema->append(SASS_MEMORY_NEW(String_Constant, pstate, str)); - } - else if (Expression_Obj tok = lex_interpolation()) { - if (String_Schema_Ptr s = Cast(tok)) { - schema->concat(s); - } else { - schema->append(tok); - } - } - else if (lex< quoted_string >()) { - Expression_Obj tok = parse_string(); - if (String_Schema_Ptr s = Cast(tok)) { - schema->concat(s); - } else { - schema->append(tok); - } - } - else { - if (peek< alternatives< exactly<'('>, exactly<'['>, exactly<'{'> > >()) { - if (lex< exactly<'('> >()) { - schema->append(SASS_MEMORY_NEW(String_Constant, pstate, std::string("("))); - if (String_Schema_Obj tok = parse_css_variable_value(false)) schema->concat(tok); - if (!lex< exactly<')'> >()) css_error("Invalid CSS", " after ", ": expected \")\", was "); - schema->append(SASS_MEMORY_NEW(String_Constant, pstate, std::string(")"))); + std::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 (Expression_Obj tok = lex_interpolation()) { + if (String_Schema* s = Cast(tok)) { + if (s->empty()) break; + schema->concat(s); + } else { + schema->append(tok); } - else if (lex< exactly<'['> >()) { - schema->append(SASS_MEMORY_NEW(String_Constant, pstate, std::string("["))); - if (String_Schema_Obj tok = parse_css_variable_value(false)) schema->concat(tok); - if (!lex< exactly<']'> >()) css_error("Invalid CSS", " after ", ": expected \"]\", was "); - schema->append(SASS_MEMORY_NEW(String_Constant, pstate, std::string("]"))); + } else if (lex< quoted_string >()) { + Expression_Obj 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< exactly<'{'> >()) { - schema->append(SASS_MEMORY_NEW(String_Constant, pstate, std::string("{"))); - if (String_Schema_Obj tok = parse_css_variable_value(false)) schema->concat(tok); - if (!lex< exactly<'}'> >()) css_error("Invalid CSS", " after ", ": expected \"}\", was "); - schema->append(SASS_MEMORY_NEW(String_Constant, pstate, std::string("}"))); + } 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, std::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)) { + std::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, std::string(1, closing_bracket))); + brackets.pop_back(); + } else { + break; } } - return schema->length() > 0 ? schema.detach() : NULL; + if (!brackets.empty()) { + std::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(); } Value_Obj Parser::parse_static_value() @@ -1883,7 +1693,7 @@ namespace Sass { return SASS_MEMORY_NEW(String_Quoted, pstate, std::string(str.begin, str.end)); } - String_Schema_Ptr schema = SASS_MEMORY_NEW(String_Schema, pstate); + 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) { @@ -1918,7 +1728,7 @@ namespace Sass { String_Obj Parser::parse_ie_keyword_arg() { - String_Schema_Ptr kwd_arg = SASS_MEMORY_NEW(String_Schema, pstate, 3); + 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 { @@ -2139,7 +1949,7 @@ namespace Sass { uri = url_string->to_string({ NESTED, 5 }); } - if (String_Schema_Ptr schema = Cast(url_string)) { + 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); @@ -2166,7 +1976,7 @@ namespace Sass { while (pp && peek< exactly< hash_lbrace > >(pp)) { pp = sequence< interpolant, real_uri_value >(pp); } - if (!pp) return 0; + if (!pp) return {}; position = pp; return parse_interpolated_chunk(Token(p, position)); } @@ -2175,7 +1985,7 @@ namespace Sass { return SASS_MEMORY_NEW(String_Constant, pstate, res); } - return 0; + return {}; } Function_Call_Obj Parser::parse_function_call() @@ -2191,18 +2001,21 @@ namespace Sass { return SASS_MEMORY_NEW(Function_Call, call_pos, name, args); } - Function_Call_Schema_Obj Parser::parse_function_call_schema() + Function_Call_Obj Parser::parse_function_call_schema() { String_Obj name = parse_identifier_schema(); ParserState source_position_of_call = pstate; Arguments_Obj args = parse_arguments(); - return SASS_MEMORY_NEW(Function_Call_Schema, source_position_of_call, name, args); + return SASS_MEMORY_NEW(Function_Call, source_position_of_call, name, args); } Content_Obj Parser::parse_content_directive() { - return SASS_MEMORY_NEW(Content, pstate); + ParserState 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) @@ -2212,7 +2025,7 @@ namespace Sass { bool root = block_stack.back()->is_root(); Expression_Obj predicate = parse_list(); Block_Obj block = parse_block(root); - Block_Obj alternative = NULL; + Block_Obj alternative; // only throw away comment if we parse a case // we want all other comments to be parsed @@ -2297,7 +2110,7 @@ namespace Sass { stack.push_back(Scope::Control); bool root = block_stack.back()->is_root(); // create the initial while call object - While_Obj call = SASS_MEMORY_NEW(While, pstate, 0, 0); + While_Obj call = SASS_MEMORY_NEW(While, pstate, {}, {}); // parse mandatory predicate Expression_Obj predicate = parse_list(); List_Obj l = Cast(predicate); @@ -2313,20 +2126,107 @@ namespace Sass { return call.detach(); } - // EO parse_while_directive - Media_Block_Obj Parser::parse_media_block() + + std::vector Parser::parseCssMediaQueries() { - stack.push_back(Scope::Media); - Media_Block_Obj media_block = SASS_MEMORY_NEW(Media_Block, pstate, 0, 0); + std::vector result; + do { + if (auto query = parseCssMediaQuery()) { + result.push_back(query); + } + } while (lex>()); + return result; + } + + std::string Parser::parseIdentifier() + { + if (lex < identifier >(false)) { + return std::string(lexed); + } + return std::string(); + } + + CssMediaQuery_Obj Parser::parseCssMediaQuery() + { + CssMediaQuery_Obj result = SASS_MEMORY_NEW(CssMediaQuery, pstate); + lex(false); + + // Check if any tokens are to parse + if (!peek_css>()) { + + std::string token1(parseIdentifier()); + lex(false); + + if (token1.empty()) { + return {}; + } - media_block->media_queries(parse_media_queries()); + std::string token2(parseIdentifier()); + lex(false); - Media_Block_Obj prev_media_block = last_media_block; - last_media_block = media_block; - media_block->block(parse_css_block()); - last_media_block = prev_media_block; + if (Util::equalsLiteral("and", token2)) { + result->type(token1); + } + else { + if (token2.empty()) { + result->type(token1); + } + else { + result->modifier(token1); + result->type(token2); + } + + if (lex < kwd_and >()) { + lex(false); + } + else { + return result; + } + + } + + } + + std::vector queries; + + do { + lex(false); + + if (lex>()) { + // In dart sass parser returns a pure string + if (lex < skip_over_scopes < exactly < '(' >, exactly < ')' > > >()) { + std::string decl("(" + std::string(lexed)); + queries.push_back(decl); + } + // Should be: parseDeclarationValue; + if (!lex>()) { + // Should we throw an error here? + } + } + } while (lex < kwd_and >()); + + result->features(queries); + + if (result->features().empty()) { + if (result->type().empty()) { + return {}; + } + } + + return result; + } + + + // EO parse_while_directive + MediaRule_Obj Parser::parseMediaRule() + { + MediaRule_Obj rule = SASS_MEMORY_NEW(MediaRule, pstate); + stack.push_back(Scope::Media); + rule->schema(parse_media_queries()); + parse_block_comments(false); + rule->block(parse_css_block()); stack.pop_back(); - return media_block.detach(); + return rule; } List_Obj Parser::parse_media_queries() @@ -2339,7 +2239,7 @@ namespace Sass { return queries.detach(); } - // Expression_Ptr Parser::parse_media_query() + // Expression* Parser::parse_media_query() Media_Query_Obj Parser::parse_media_query() { advanceToNextToken(); @@ -2353,7 +2253,7 @@ namespace Sass { while (lex_css < kwd_and >()) media_query->append(parse_media_expression()); if (lex < identifier_schema >()) { - String_Schema_Ptr schema = SASS_MEMORY_NEW(String_Schema, pstate); + String_Schema* schema = SASS_MEMORY_NEW(String_Schema, pstate); schema->append(media_query->media_type()); schema->append(SASS_MEMORY_NEW(String_Constant, pstate, " ")); schema->append(parse_identifier_schema()); @@ -2370,7 +2270,7 @@ namespace Sass { { if (lex < identifier_schema >()) { String_Obj ss = parse_identifier_schema(); - return SASS_MEMORY_NEW(Media_Query_Expression, pstate, ss, 0, true); + return SASS_MEMORY_NEW(Media_Query_Expression, pstate, ss, {}, true); } if (!lex_css< exactly<'('> >()) { error("media query expression must begin with '('"); @@ -2380,7 +2280,7 @@ namespace Sass { error("media feature required in media query expression"); } feature = parse_expression(); - Expression_Obj expression = 0; + Expression_Obj expression; if (lex_css< exactly<':'> >()) { expression = parse_list(DELAYED); } @@ -2394,10 +2294,7 @@ namespace Sass { // these are very similar to media blocks Supports_Block_Obj Parser::parse_supports_directive() { - Supports_Condition_Obj cond = parse_supports_condition(); - if (!cond) { - css_error("Invalid CSS", " after ", ": expected @supports condition (e.g. (display: flexbox)), was ", false); - } + Supports_Condition_Obj cond = parse_supports_condition(/*top_level=*/true); // create the ast node object for the support queries Supports_Block_Obj query = SASS_MEMORY_NEW(Supports_Block, pstate, cond); // additional block is mandatory @@ -2409,27 +2306,27 @@ namespace Sass { // parse one query operation // may encounter nested queries - Supports_Condition_Obj Parser::parse_supports_condition() + Supports_Condition_Obj Parser::parse_supports_condition(bool top_level) { lex < css_whitespace >(); Supports_Condition_Obj cond; if ((cond = parse_supports_negation())) return cond; - if ((cond = parse_supports_operator())) return cond; + if ((cond = parse_supports_operator(top_level))) return cond; if ((cond = parse_supports_interpolation())) return cond; return cond; } Supports_Condition_Obj Parser::parse_supports_negation() { - if (!lex < kwd_not >()) return 0; - Supports_Condition_Obj cond = parse_supports_condition_in_parens(); + if (!lex < kwd_not >()) return {}; + Supports_Condition_Obj cond = parse_supports_condition_in_parens(/*parens_required=*/true); return SASS_MEMORY_NEW(Supports_Negation, pstate, cond); } - Supports_Condition_Obj Parser::parse_supports_operator() + Supports_Condition_Obj Parser::parse_supports_operator(bool top_level) { - Supports_Condition_Obj cond = parse_supports_condition_in_parens(); - if (cond.isNull()) return 0; + Supports_Condition_Obj cond = parse_supports_condition_in_parens(/*parens_required=*/top_level); + if (cond.isNull()) return {}; while (true) { Supports_Operator::Operand op = Supports_Operator::OR; @@ -2437,9 +2334,9 @@ namespace Sass { else if(!lex < kwd_or >()) { break; } lex < css_whitespace >(); - Supports_Condition_Obj right = parse_supports_condition_in_parens(); + Supports_Condition_Obj right = parse_supports_condition_in_parens(/*parens_required=*/true); - // Supports_Condition_Ptr cc = SASS_MEMORY_NEW(Supports_Condition, *static_cast(cond)); + // Supports_Condition* cc = SASS_MEMORY_NEW(Supports_Condition, *static_cast(cond)); cond = SASS_MEMORY_NEW(Supports_Operator, pstate, cond, right, op); } return cond; @@ -2447,10 +2344,10 @@ namespace Sass { Supports_Condition_Obj Parser::parse_supports_interpolation() { - if (!lex < interpolant >()) return 0; + if (!lex < interpolant >()) return {}; String_Obj interp = parse_interpolated_chunk(lexed); - if (!interp) return 0; + if (!interp) return {}; return SASS_MEMORY_NEW(Supports_Interpolation, pstate, interp); } @@ -2459,10 +2356,10 @@ namespace Sass { // look like declarations their semantics differ significantly Supports_Condition_Obj Parser::parse_supports_declaration() { - Supports_Condition_Ptr cond; + Supports_Condition* cond; // parse something declaration like Expression_Obj feature = parse_expression(); - Expression_Obj expression = 0; + Expression_Obj expression; if (lex_css< exactly<':'> >()) { expression = parse_list(DELAYED); } @@ -2475,21 +2372,24 @@ namespace Sass { return cond; } - Supports_Condition_Obj Parser::parse_supports_condition_in_parens() + Supports_Condition_Obj Parser::parse_supports_condition_in_parens(bool parens_required) { Supports_Condition_Obj interp = parse_supports_interpolation(); - if (interp != 0) return interp; + if (interp != nullptr) return interp; - if (!lex < exactly <'('> >()) return 0; + 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 >(); - Supports_Condition_Obj cond = parse_supports_condition(); - if (cond != 0) { - if (!lex < exactly <')'> >()) error("unclosed parenthesis in @supports declaration"); - } else { - cond = parse_supports_declaration(); - if (!lex < exactly <')'> >()) error("unclosed parenthesis in @supports declaration"); - } + Supports_Condition_Obj 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; } @@ -2498,7 +2398,7 @@ namespace Sass { { stack.push_back(Scope::AtRoot); ParserState at_source_position = pstate; - Block_Obj body = 0; + Block_Obj body; At_Root_Query_Obj expr; Lookahead lookahead_result; if (lex_css< exactly<'('> >()) { @@ -2545,68 +2445,6 @@ namespace Sass { return cond; } - Directive_Obj Parser::parse_special_directive() - { - std::string kwd(lexed); - - if (lexed == "@else") error("Invalid CSS: @else must come after @if"); - - // this whole branch is never hit via spec tests - - Directive_Ptr at_rule = SASS_MEMORY_NEW(Directive, pstate, kwd); - Lookahead lookahead = lookahead_for_include(position); - if (lookahead.found && !lookahead.has_interpolants) { - at_rule->selector(parse_selector_list(false)); - } - - lex < css_comments >(false); - - if (lex < static_property >()) { - at_rule->value(parse_interpolated_chunk(Token(lexed))); - } else if (!(peek < alternatives < exactly<'{'>, exactly<'}'>, exactly<';'> > >())) { - at_rule->value(parse_list()); - } - - lex < css_comments >(false); - - if (peek< exactly<'{'> >()) { - at_rule->block(parse_block()); - } - - return at_rule; - } - - // this whole branch is never hit via spec tests - Directive_Obj Parser::parse_prefixed_directive() - { - std::string kwd(lexed); - - if (lexed == "@else") error("Invalid CSS: @else must come after @if"); - - Directive_Obj at_rule = SASS_MEMORY_NEW(Directive, pstate, kwd); - Lookahead lookahead = lookahead_for_include(position); - if (lookahead.found && !lookahead.has_interpolants) { - at_rule->selector(parse_selector_list(false)); - } - - lex < css_comments >(false); - - if (lex < static_property >()) { - at_rule->value(parse_interpolated_chunk(Token(lexed))); - } else if (!(peek < alternatives < exactly<'{'>, exactly<'}'>, exactly<';'> > >())) { - at_rule->value(parse_list()); - } - - lex < css_comments >(false); - - if (peek< exactly<'{'> >()) { - at_rule->block(parse_block()); - } - - return at_rule; - } - - Directive_Obj Parser::parse_directive() { Directive_Obj directive = SASS_MEMORY_NEW(Directive, pstate, lexed); @@ -2624,7 +2462,7 @@ namespace Sass { if (lex < interpolant >(true) != NULL) { return parse_interpolated_chunk(lexed, true); } - return 0; + return {}; } Expression_Obj Parser::lex_interp_uri() @@ -2647,6 +2485,7 @@ namespace Sass { lex < one_plus < alternatives < + exactly <'>'>, sequence < exactly <'\\'>, any_char @@ -2690,13 +2529,13 @@ namespace Sass { if (match) { return SASS_MEMORY_NEW(String_Constant, pstate, lexed); } - return NULL; + return {}; } Expression_Obj Parser::lex_almost_any_value_token() { Expression_Obj rv; - if (*position == 0) return 0; + 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; @@ -2712,10 +2551,10 @@ namespace Sass { { String_Schema_Obj schema = SASS_MEMORY_NEW(String_Schema, pstate); - if (*position == 0) return 0; + if (*position == 0) return {}; lex < spaces >(false); Expression_Obj token = lex_almost_any_value_token(); - if (!token) return 0; + if (!token) return {}; schema->append(token); if (*position == 0) { schema->rtrim(); @@ -2813,7 +2652,7 @@ namespace Sass { rv.error = q; rv.position = q; // check expected opening bracket - // only after successfull matching + // 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; @@ -2997,7 +2836,7 @@ namespace Sass { Expression_Obj Parser::fold_operands(Expression_Obj base, std::vector& operands, std::vector& ops, size_t i) { - if (String_Schema_Ptr schema = Cast(base)) { + if (String_Schema* schema = Cast(base)) { // return schema; if (schema->has_interpolants()) { if (i + 1 < operands.size() && ( @@ -3020,7 +2859,7 @@ namespace Sass { } for (size_t S = operands.size(); i < S; ++i) { - if (String_Schema_Ptr schema = Cast(operands[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 @@ -3037,13 +2876,13 @@ namespace Sass { } else { base = SASS_MEMORY_NEW(Binary_Expression, base->pstate(), ops[i], base, operands[i]); } - Binary_Expression_Ptr b = Cast(base.ptr()); + 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_Ptr b = Cast(base)) { + if (Binary_Expression* b = Cast(base)) { if (Cast(b->left())) base->set_delayed(false); if (Cast(b->right())) base->set_delayed(false); } @@ -3054,8 +2893,15 @@ namespace Sass { { Position p(pos.line ? pos : before_token); ParserState pstate(path, source, p, Offset(0, 0)); + // `pstate.src` may not outlive stack unwind so we must copy it. + // This is needed since we often parse dynamically generated code, + // e.g. for interpolations, and we normally don't want to keep this + // memory around after we parsed the AST tree successfully. Only on + // errors we want to preserve them for better error reporting. + char *src_copy = sass_copy_c_string(pstate.src); + pstate.src = src_copy; traces.push_back(Backtrace(pstate)); - throw Exception::InvalidSass(pstate, traces, msg); + throw Exception::InvalidSass(pstate, traces, msg, src_copy); } void Parser::error(std::string msg) @@ -3078,7 +2924,7 @@ namespace Sass { } // backup position to last significant char while (trim && last_pos > source && last_pos < end) { - if (!Prelexer::is_space(*last_pos)) break; + if (!Util::ascii_isspace(static_cast(*last_pos))) break; utf8::prior(last_pos, source); } diff --git a/src/libsass/src/parser.hpp b/src/libsass/src/parser.hpp index d2a6ddc1a..8f0b5f1ae 100644 --- a/src/libsass/src/parser.hpp +++ b/src/libsass/src/parser.hpp @@ -1,6 +1,10 @@ #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 @@ -38,7 +42,6 @@ namespace Sass { Context& ctx; std::vector block_stack; std::vector stack; - Media_Block_Ptr last_media_block; const char* source; const char* position; const char* end; @@ -48,23 +51,24 @@ namespace Sass { Backtraces traces; size_t indentation; size_t nestings; + bool allow_parent; Token lexed; - Parser(Context& ctx, const ParserState& pstate, Backtraces traces) - : ParserState(pstate), ctx(ctx), block_stack(), stack(0), last_media_block(), + Parser(Context& ctx, const ParserState& pstate, Backtraces traces, bool allow_parent = true) + : ParserState(pstate), ctx(ctx), block_stack(), stack(0), source(0), position(0), end(0), before_token(pstate), after_token(pstate), - pstate(pstate), traces(traces), indentation(0), nestings(0) - { + pstate(pstate), traces(traces), indentation(0), nestings(0), allow_parent(allow_parent) + { stack.push_back(Scope::Root); } // static Parser from_string(const std::string& src, Context& ctx, ParserState pstate = ParserState("[STRING]")); - static Parser from_c_str(const char* src, Context& ctx, Backtraces, ParserState pstate = ParserState("[CSTRING]"), const char* source = 0); - static Parser from_c_str(const char* beg, const char* end, Context& ctx, Backtraces, ParserState pstate = ParserState("[CSTRING]"), const char* source = 0); - static Parser from_token(Token t, Context& ctx, Backtraces, ParserState pstate = ParserState("[TOKEN]"), const char* source = 0); + static Parser from_c_str(const char* src, Context& ctx, Backtraces, ParserState pstate = ParserState("[CSTRING]"), const char* source = nullptr, bool allow_parent = true); + static Parser from_c_str(const char* beg, const char* end, Context& ctx, Backtraces, ParserState pstate = ParserState("[CSTRING]"), const char* source = nullptr, bool allow_parent = true); + static Parser from_token(Token t, Context& ctx, Backtraces, ParserState pstate = ParserState("[TOKEN]"), const char* source = nullptr); // special static parsers to convert strings into certain selectors - static Selector_List_Obj parse_selector(const char* src, Context& ctx, Backtraces, ParserState pstate = ParserState("[SELECTOR]"), const char* source = 0); + static SelectorListObj parse_selector(const char* src, Context& ctx, Backtraces, ParserState pstate = ParserState("[SELECTOR]"), const char* source = nullptr, bool allow_parent = true); #ifdef __clang__ @@ -184,7 +188,7 @@ namespace Sass { // update after_token position for current token after_token.add(it_before_token, it_after_token); - // ToDo: could probably do this incremetal on original object (API wants offset?) + // ToDo: could probably do this incremental on original object (API wants offset?) pstate = ParserState(path, source, lexed, before_token, after_token - before_token); // advance internal char iterator @@ -256,20 +260,20 @@ namespace Sass { Argument_Obj parse_argument(); Assignment_Obj parse_assignment(); Ruleset_Obj parse_ruleset(Lookahead lookahead); - Selector_List_Obj parse_selector_list(bool chroot); - Complex_Selector_Obj parse_complex_selector(bool chroot); + SelectorListObj parseSelectorList(bool chroot); + ComplexSelectorObj parseComplexSelector(bool chroot); Selector_Schema_Obj parse_selector_schema(const char* end_of_selector, bool chroot); - Compound_Selector_Obj parse_compound_selector(); - Simple_Selector_Obj parse_simple_selector(); - Wrapped_Selector_Obj parse_negated_selector(); - Simple_Selector_Obj parse_pseudo_selector(); + CompoundSelectorObj parseCompoundSelector(); + SimpleSelectorObj parse_simple_selector(); + Pseudo_Selector_Obj parse_negated_selector2(); + Expression* parse_binominal(); + SimpleSelectorObj parse_pseudo_selector(); Attribute_Selector_Obj parse_attribute_selector(); Block_Obj parse_block(bool is_root = false); Block_Obj parse_css_block(bool is_root = false); bool parse_block_nodes(bool is_root = false); bool parse_block_node(bool is_root = false); - bool parse_number_prefix(); Declaration_Obj parse_declaration(); Expression_Obj parse_map(); Expression_Obj parse_bracket_list(); @@ -285,14 +289,13 @@ namespace Sass { Expression_Obj parse_value(); Function_Call_Obj parse_calc_function(); Function_Call_Obj parse_function_call(); - Function_Call_Schema_Obj parse_function_call_schema(); + 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(); Value_Obj parse_static_value(); - String_Schema_Obj parse_css_variable_value(bool top_level = true); - String_Schema_Obj parse_css_variable_value_token(bool top_level = true); + 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); @@ -301,31 +304,32 @@ namespace Sass { For_Obj parse_for_directive(); Each_Obj parse_each_directive(); While_Obj parse_while_directive(); + MediaRule_Obj parseMediaRule(); + std::vector parseCssMediaQueries(); + std::string parseIdentifier(); + CssMediaQuery_Obj parseCssMediaQuery(); Return_Obj parse_return_directive(); Content_Obj parse_content_directive(); void parse_charset_directive(); - Media_Block_Obj parse_media_block(); List_Obj parse_media_queries(); Media_Query_Obj parse_media_query(); Media_Query_Expression_Obj parse_media_expression(); Supports_Block_Obj parse_supports_directive(); - Supports_Condition_Obj parse_supports_condition(); + Supports_Condition_Obj parse_supports_condition(bool top_level); Supports_Condition_Obj parse_supports_negation(); - Supports_Condition_Obj parse_supports_operator(); + Supports_Condition_Obj parse_supports_operator(bool top_level); Supports_Condition_Obj parse_supports_interpolation(); Supports_Condition_Obj parse_supports_declaration(); - Supports_Condition_Obj parse_supports_condition_in_parens(); + Supports_Condition_Obj parse_supports_condition_in_parens(bool parens_required); At_Root_Block_Obj parse_at_root_block(); At_Root_Query_Obj parse_at_root_query(); String_Schema_Obj parse_almost_any_value(); - Directive_Obj parse_special_directive(); - Directive_Obj parse_prefixed_directive(); Directive_Obj parse_directive(); Warning_Obj parse_warning(); Error_Obj parse_error(); Debug_Obj parse_debug(); - Value_Ptr color_or_string(const std::string& lexed) const; + Value* color_or_string(const std::string& lexed) const; // be more like ruby sass Expression_Obj lex_almost_any_value_token(); @@ -338,7 +342,7 @@ namespace Sass { Token lex_variable(); Token lex_identifier(); - void parse_block_comments(); + void parse_block_comments(bool store = true); Lookahead lookahead_for_value(const char* start = 0); Lookahead lookahead_for_selector(const char* start = 0); @@ -375,19 +379,19 @@ namespace Sass { return SASS_MEMORY_NEW(String_Constant, pstate, lexed); } } - return 0; + return {}; } public: - static Number_Ptr lexed_number(const ParserState& pstate, const std::string& parsed); - static Number_Ptr lexed_dimension(const ParserState& pstate, const std::string& parsed); - static Number_Ptr lexed_percentage(const ParserState& pstate, const std::string& parsed); - static Value_Ptr lexed_hex_color(const ParserState& pstate, const std::string& parsed); + static Number* lexed_number(const ParserState& pstate, const std::string& parsed); + static Number* lexed_dimension(const ParserState& pstate, const std::string& parsed); + static Number* lexed_percentage(const ParserState& pstate, const std::string& parsed); + static Value* lexed_hex_color(const ParserState& pstate, const std::string& parsed); private: - Number_Ptr lexed_number(const std::string& parsed) { return lexed_number(pstate, parsed); }; - Number_Ptr lexed_dimension(const std::string& parsed) { return lexed_dimension(pstate, parsed); }; - Number_Ptr lexed_percentage(const std::string& parsed) { return lexed_percentage(pstate, parsed); }; - Value_Ptr lexed_hex_color(const std::string& parsed) { return lexed_hex_color(pstate, parsed); }; + Number* lexed_number(const std::string& parsed) { return lexed_number(pstate, parsed); }; + Number* lexed_dimension(const std::string& parsed) { return lexed_dimension(pstate, parsed); }; + Number* lexed_percentage(const std::string& parsed) { return lexed_percentage(pstate, parsed); }; + Value* lexed_hex_color(const std::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); diff --git a/src/libsass/src/parser_selectors.cpp b/src/libsass/src/parser_selectors.cpp new file mode 100644 index 000000000..3b4a6e2d9 --- /dev/null +++ b/src/libsass/src/parser_selectors.cpp @@ -0,0 +1,189 @@ +// sass.hpp must go before all system headers to get the +// __EXTENSIONS__ fix on Solaris. +#include "sass.hpp" + +#include "parser.hpp" + +namespace Sass { + + using namespace Prelexer; + using namespace Constants; + + ComplexSelectorObj Parser::parseComplexSelector(bool chroot) + { + + NESTING_GUARD(nestings); + + lex < block_comment >(); + advanceToNextToken(); + + ComplexSelectorObj sel = SASS_MEMORY_NEW(ComplexSelector, pstate); + + if (peek < end_of_file >()) return sel; + + while (true) { + + lex < block_comment >(); + advanceToNextToken(); + + // check for child (+) combinator + if (lex < exactly < selector_combinator_child > >()) { + sel->append(SASS_MEMORY_NEW(SelectorCombinator, pstate, SelectorCombinator::CHILD, peek_newline())); + } + // check for general sibling (~) combinator + else if (lex < exactly < selector_combinator_general > >()) { + sel->append(SASS_MEMORY_NEW(SelectorCombinator, pstate, SelectorCombinator::GENERAL, peek_newline())); + } + // check for adjecant sibling (+) combinator + else if (lex < exactly < selector_combinator_adjacent > >()) { + sel->append(SASS_MEMORY_NEW(SelectorCombinator, pstate, SelectorCombinator::ADJACENT, peek_newline())); + } + // check if we can parse a compound selector + else if (CompoundSelectorObj compound = parseCompoundSelector()) { + sel->append(compound); + } + else { + break; + } + } + + if (sel->empty()) return {}; + + // check if we parsed any parent references + sel->chroots(sel->has_real_parent_ref() || chroot); + + sel->update_pstate(pstate); + + return sel; + + } + + SelectorListObj Parser::parseSelectorList(bool chroot) + { + + bool reloop; + bool had_linefeed = false; + NESTING_GUARD(nestings); + SelectorListObj list = SASS_MEMORY_NEW(SelectorList, pstate); + + if (peek_css< alternatives < end_of_file, exactly <'{'>, exactly <','> > >()) { + css_error("Invalid CSS", " after ", ": expected selector, was "); + } + + do { + reloop = false; + + had_linefeed = had_linefeed || peek_newline(); + + if (peek_css< alternatives < class_char < selector_list_delims > > >()) + break; // in case there are superfluous commas at the end + + // now parse the complex selector + ComplexSelectorObj complex = parseComplexSelector(chroot); + if (complex.isNull()) return list.detach(); + complex->hasPreLineFeed(had_linefeed); + + had_linefeed = false; + + while (peek_css< exactly<','> >()) + { + lex< css_comments >(false); + // consume everything up and including the comma separator + reloop = lex< exactly<','> >() != 0; + // remember line break (also between some commas) + had_linefeed = had_linefeed || peek_newline(); + // remember line break (also between some commas) + } + list->append(complex); + + } while (reloop); + + while (lex_css< kwd_optional >()) { + list->is_optional(true); + } + + // update for end position + list->update_pstate(pstate); + + return list.detach(); + } + + // parse one compound selector, which is basically + // a list of simple selectors (directly adjacent) + // lex them exactly (without skipping white-space) + CompoundSelectorObj Parser::parseCompoundSelector() + { + // init an empty compound selector wrapper + CompoundSelectorObj seq = SASS_MEMORY_NEW(CompoundSelector, pstate); + + // skip initial white-space + lex < block_comment >(); + advanceToNextToken(); + + if (lex< exactly<'&'> >(false)) + { + // ToDo: check the conditions and try to simplify flag passing + if (!allow_parent) error("Parent selectors aren't allowed here."); + // Create and append a new parent selector object + seq->hasRealParent(true); + } + + // parse list + while (true) + { + // remove all block comments + // leaves trailing white-space + lex < block_comment >(); + // parse parent selector + if (lex< exactly<'&'> >(false)) + { + // parent selector only allowed at start + // upcoming Sass may allow also trailing + ParserState state(pstate); + std::string found("&"); + if (lex < identifier >()) { + found += std::string(lexed); + } + std::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.", state); + } + // parse functional + else if (match < re_functional >()) + { + seq->append(parse_simple_selector()); + } + + // parse type selector + else if (lex< re_type_selector >(false)) + { + seq->append(SASS_MEMORY_NEW(Type_Selector, pstate, lexed)); + } + // peek for abort conditions + else if (peek< spaces >()) break; + else if (peek< end_of_file >()) { break; } + else if (peek_css < class_char < selector_combinator_ops > >()) break; + else if (peek_css < class_char < complex_selector_delims > >()) break; + // otherwise parse another simple selector + else { + SimpleSelectorObj sel = parse_simple_selector(); + if (!sel) return {}; + seq->append(sel); + } + } + // EO while true + + if (seq && !peek_css>>()) { + seq->hasPostLineBreak(peek_newline()); + } + + // We may have set hasRealParent + if (seq && seq->empty() && !seq->hasRealParent()) return {}; + + return seq; + } + + +} diff --git a/src/libsass/src/paths.hpp b/src/libsass/src/paths.hpp deleted file mode 100644 index aabab94ae..000000000 --- a/src/libsass/src/paths.hpp +++ /dev/null @@ -1,71 +0,0 @@ -#ifndef SASS_PATHS_H -#define SASS_PATHS_H - -#include -#include -#include - - -template -std::string vector_to_string(std::vector v) -{ - std::stringstream buffer; - buffer << "["; - - if (!v.empty()) - { buffer << v[0]; } - else - { buffer << "]"; } - - if (v.size() == 1) - { buffer << "]"; } - else - { - for (size_t i = 1, S = v.size(); i < S; ++i) buffer << ", " << v[i]; - buffer << "]"; - } - - return buffer.str(); -} - -namespace Sass { - - - template - std::vector > paths(std::vector > strata, size_t from_end = 0) - { - if (strata.empty()) { - return std::vector >(); - } - - size_t end = strata.size() - from_end; - if (end <= 1) { - std::vector > starting_points; - starting_points.reserve(strata[0].size()); - for (size_t i = 0, S = strata[0].size(); i < S; ++i) { - std::vector starting_point; - starting_point.push_back(strata[0][i]); - starting_points.push_back(starting_point); - } - return starting_points; - } - - std::vector > up_to_here = paths(strata, from_end + 1); - std::vector here = strata[end-1]; - - std::vector > branches; - branches.reserve(up_to_here.size() * here.size()); - for (size_t i = 0, S1 = up_to_here.size(); i < S1; ++i) { - for (size_t j = 0, S2 = here.size(); j < S2; ++j) { - std::vector branch = up_to_here[i]; - branch.push_back(here[j]); - branches.push_back(branch); - } - } - - return branches; - } - -} - -#endif diff --git a/src/libsass/src/permutate.hpp b/src/libsass/src/permutate.hpp new file mode 100644 index 000000000..01077940c --- /dev/null +++ b/src/libsass/src/permutate.hpp @@ -0,0 +1,140 @@ +#ifndef SASS_PATHS_H +#define SASS_PATHS_H + +#include + +namespace Sass { + + // Returns a list of all possible paths through the given lists. + // + // For example, given `[[1, 2], [3, 4], [5]]`, this returns: + // + // ``` + // [[1, 3, 5], + // [2, 3, 5], + // [1, 4, 5], + // [2, 4, 5]] + // ``` + // + // Note: called `paths` in dart-sass + template + std::vector> permutate( + const std::vector>& in) + { + + size_t L = in.size(), n = 0; + + // Exit early if any entry is empty + for (size_t i = 0; i < L; i += 1) { + if (in[i].size() == 0) return {}; + } + + size_t* state = new size_t[L + 1]; + std::vector> out; + + // First initialize all states for every permutation group + for (size_t i = 0; i < L; i += 1) { + state[i] = in[i].size() - 1; + } + while (true) { + std::vector perm; + // Create one permutation for state + for (size_t i = 0; i < L; i += 1) { + perm.push_back(in.at(i).at(in[i].size() - state[i] - 1)); + } + // Current group finished + if (state[n] == 0) { + // Find position of next decrement + while (n < L && state[++n] == 0) {} + + if (n == L) { + out.push_back(perm); + break; + } + + state[n] -= 1; + + for (size_t p = 0; p < n; p += 1) { + state[p] = in[p].size() - 1; + } + + // Restart from front + n = 0; + + } + else { + state[n] -= 1; + } + out.push_back(perm); + } + + delete[] state; + return out; + } + // EO permutate + + // ToDo: this variant is used in resolve_parent_refs + template + std::vector> + permutateAlt(const std::vector>& in) { + + size_t L = in.size(); + size_t n = in.size() - 1; + size_t* state = new size_t[L]; + std::vector< std::vector> out; + + // First initialize all states for every permutation group + for (size_t i = 0; i < L; i += 1) { + if (in[i].size() == 0) return {}; + state[i] = in[i].size() - 1; + } + + while (true) { + /* + // std::cerr << "PERM: "; + for (size_t p = 0; p < L; p++) + { // std::cerr << state[p] << " "; } + // std::cerr << "\n"; + */ + std::vector perm; + // Create one permutation for state + for (size_t i = 0; i < L; i += 1) { + perm.push_back(in.at(i).at(in[i].size() - state[i] - 1)); + } + // Current group finished + if (state[n] == 0) { + // Find position of next decrement + while (n > 0 && state[--n] == 0) + { + + } + // Check for end condition + if (state[n] != 0) { + // Decrease next on the left side + state[n] -= 1; + // Reset all counters to the right + for (size_t p = n + 1; p < L; p += 1) { + state[p] = in[p].size() - 1; + } + // Restart from end + n = L - 1; + } + else { + out.push_back(perm); + break; + } + } + else { + state[n] -= 1; + } + out.push_back(perm); + } + + delete[] state; + return out; + } + // EO permutateAlt + +} + +#endif diff --git a/src/libsass/src/plugins.cpp b/src/libsass/src/plugins.cpp index eecba7880..b6f004242 100644 --- a/src/libsass/src/plugins.cpp +++ b/src/libsass/src/plugins.cpp @@ -1,7 +1,11 @@ +// 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" #ifdef _WIN32 #include @@ -154,7 +158,7 @@ namespace Sass { } } } - catch (utf8::invalid_utf8) + catch (utf8::invalid_utf8&) { // report the error to the console (should not happen) // implementors should make sure to provide valid utf8 diff --git a/src/libsass/src/position.cpp b/src/libsass/src/position.cpp index 312e04ca6..de8f83135 100644 --- a/src/libsass/src/position.cpp +++ b/src/libsass/src/position.cpp @@ -1,4 +1,7 @@ +// sass.hpp must go before all system headers to get the +// __EXTENSIONS__ fix on Solaris. #include "sass.hpp" + #include "position.hpp" namespace Sass { diff --git a/src/libsass/src/position.hpp b/src/libsass/src/position.hpp index 923be3c59..3e3a1c203 100644 --- a/src/libsass/src/position.hpp +++ b/src/libsass/src/position.hpp @@ -86,7 +86,7 @@ namespace Sass { size_t length() const { return end - begin; } std::string ws_before() const { return std::string(prefix, begin); } - const std::string to_string() const { return std::string(begin, end); } + std::string to_string() const { return std::string(begin, end); } std::string time_wspace() const { std::string str(to_string()); std::string whitespaces(" \t\f\v\n\r"); diff --git a/src/libsass/src/prelexer.cpp b/src/libsass/src/prelexer.cpp index a43b1ee3c..acd3061e3 100644 --- a/src/libsass/src/prelexer.cpp +++ b/src/libsass/src/prelexer.cpp @@ -1,8 +1,11 @@ +// sass.hpp must go before all system headers to get the +// __EXTENSIONS__ fix on Solaris. #include "sass.hpp" -#include + #include #include #include "util.hpp" +#include "util_string.hpp" #include "position.hpp" #include "prelexer.hpp" #include "constants.hpp" @@ -260,7 +263,7 @@ namespace Sass { >(src); } - // Match a line comment (/.*?(?=\n|\r\n?|\Z)/. + // Match a line comment (/.*?(?=\n|\r\n?|\f|\Z)/. const char* line_comment(const char* src) { return sequence< @@ -333,7 +336,7 @@ namespace Sass { return alternatives< unicode_seq, alpha, - unicode, + nonascii, exactly<'-'>, exactly<'_'>, NONASCII, @@ -348,7 +351,7 @@ namespace Sass { return alternatives< unicode_seq, alnum, - unicode, + nonascii, exactly<'-'>, exactly<'_'>, NONASCII, @@ -382,7 +385,7 @@ namespace Sass { { return alternatives < alpha, - unicode, + nonascii, escape_seq, exactly<'_'> >(src); @@ -392,7 +395,7 @@ namespace Sass { { return alternatives < alnum, - unicode, + nonascii, escape_seq, exactly<'_'> >(src); @@ -1260,6 +1263,9 @@ namespace Sass { 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) { @@ -1394,7 +1400,7 @@ namespace Sass { }*/ const char* H(const char* src) { - return std::isxdigit(*src) ? src+1 : 0; + return Util::ascii_isxdigit(static_cast(*src)) ? src+1 : 0; } const char* W(const char* src) { diff --git a/src/libsass/src/prelexer.hpp b/src/libsass/src/prelexer.hpp index 2d8f83164..07ad09c11 100644 --- a/src/libsass/src/prelexer.hpp +++ b/src/libsass/src/prelexer.hpp @@ -25,6 +25,7 @@ namespace Sass { 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); @@ -64,16 +65,15 @@ namespace Sass { size_t level = 0; bool in_squote = false; bool in_dquote = false; - // bool in_braces = false; - - while (*src) { - - // check for abort condition - if (end && src >= end) break; + bool in_backslash_escape = false; + while ((end == nullptr || src < end) && *src != '\0') { // has escaped sequence? - if (*src == '\\') { - ++ src; // skip this (and next) + if (in_backslash_escape) { + in_backslash_escape = false; + } + else if (*src == '\\') { + in_backslash_escape = true; } else if (*src == '"') { in_dquote = ! in_dquote; @@ -119,7 +119,7 @@ namespace Sass { // first start/opener must be consumed already! template const char* skip_over_scopes(const char* src) { - return skip_over_scopes(src, 0); + return skip_over_scopes(src, nullptr); } // Match a sequence of characters delimited by the supplied chars. diff --git a/src/libsass/src/remove_placeholders.cpp b/src/libsass/src/remove_placeholders.cpp index 15cddace2..6b2c57723 100644 --- a/src/libsass/src/remove_placeholders.cpp +++ b/src/libsass/src/remove_placeholders.cpp @@ -1,84 +1,86 @@ +// sass.hpp must go before all system headers to get the +// __EXTENSIONS__ fix on Solaris. #include "sass.hpp" +#include "ast.hpp" + #include "remove_placeholders.hpp" -#include "context.hpp" -#include "inspect.hpp" -#include namespace Sass { Remove_Placeholders::Remove_Placeholders() { } - void Remove_Placeholders::operator()(Block_Ptr b) { - for (size_t i = 0, L = b->length(); i < L; ++i) { - Statement_Ptr st = b->at(i); - st->perform(this); - } + 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); + } } - Selector_List_Ptr Remove_Placeholders::remove_placeholders(Selector_List_Ptr sl) + void Remove_Placeholders::remove_placeholders(SimpleSelector* simple) { - Selector_List_Ptr new_sl = SASS_MEMORY_NEW(Selector_List, sl->pstate()); - - for (size_t i = 0, L = sl->length(); i < L; ++i) { - if (!sl->at(i)->contains_placeholder()) { - new_sl->append(sl->at(i)); - } + if (Pseudo_Selector * pseudo = simple->getPseudoSelector()) { + if (pseudo->selector()) remove_placeholders(pseudo->selector()); } - - return new_sl; - } + void Remove_Placeholders::remove_placeholders(CompoundSelector* compound) + { + for (size_t i = 0, L = compound->length(); i < L; ++i) { + if (compound->get(i)) remove_placeholders(compound->get(i)); + } + listEraseItemIf(compound->elements(), listIsEmpty); + } - void Remove_Placeholders::operator()(Ruleset_Ptr r) { - // Create a new selector group without placeholders - Selector_List_Obj sl = Cast(r->selector()); - - if (sl) { - // Set the new placeholder selector list - r->selector(remove_placeholders(sl)); - // Remove placeholders in wrapped selectors - for (Complex_Selector_Obj cs : sl->elements()) { - while (cs) { - if (cs->head()) { - for (Simple_Selector_Obj& ss : cs->head()->elements()) { - if (Wrapped_Selector_Ptr ws = Cast(ss)) { - if (Selector_List_Ptr wsl = Cast(ws->selector())) { - Selector_List_Ptr clean = remove_placeholders(wsl); - // also clean superflous parent selectors - // probably not really the correct place - clean->remove_parent_selectors(); - ws->selector(clean); - } - } - } - } - cs = cs->tail(); - } + void Remove_Placeholders::remove_placeholders(ComplexSelector* complex) + { + if (complex->has_placeholder()) { + complex->clear(); // remove all + } + else { + for (size_t i = 0, L = complex->length(); i < L; ++i) { + if (CompoundSelector * compound = complex->get(i)->getCompound()) { + if (compound) remove_placeholders(compound); } } + listEraseItemIf(complex->elements(), listIsEmpty); + } + } - // Iterate into child blocks - Block_Obj b = r->block(); + SelectorList* Remove_Placeholders::remove_placeholders(SelectorList* sl) + { + for (size_t i = 0, L = sl->length(); i < L; ++i) { + if (sl->get(i)) remove_placeholders(sl->get(i)); + } + listEraseItemIf(sl->elements(), listIsEmpty); + return sl; + } - for (size_t i = 0, L = b->length(); i < L; ++i) { - if (b->at(i)) { - Statement_Obj st = b->at(i); - st->perform(this); - } - } + void Remove_Placeholders::operator()(CssMediaRule* rule) + { + if (rule->block()) operator()(rule->block()); } - void Remove_Placeholders::operator()(Media_Block_Ptr m) { - operator()(m->block()); + void Remove_Placeholders::operator()(Ruleset* r) + { + if (SelectorListObj sl = r->selector()) { + // Set the new placeholder selector list + r->selector((remove_placeholders(sl))); + } + // Iterate into child blocks + Block_Obj b = r->block(); + for (size_t i = 0, L = b->length(); i < L; ++i) { + if (b->get(i)) { b->get(i)->perform(this); } + } } - void Remove_Placeholders::operator()(Supports_Block_Ptr m) { - operator()(m->block()); + + void Remove_Placeholders::operator()(Supports_Block* m) + { + if (m->block()) operator()(m->block()); } - void Remove_Placeholders::operator()(Directive_Ptr a) { - if (a->block()) a->block()->perform(this); + void Remove_Placeholders::operator()(Directive* a) + { + if (a->block()) a->block()->perform(this); } } diff --git a/src/libsass/src/remove_placeholders.hpp b/src/libsass/src/remove_placeholders.hpp index c13b63134..34654d3fa 100644 --- a/src/libsass/src/remove_placeholders.hpp +++ b/src/libsass/src/remove_placeholders.hpp @@ -1,34 +1,36 @@ #ifndef SASS_REMOVE_PLACEHOLDERS_H #define SASS_REMOVE_PLACEHOLDERS_H -#pragma once - -#include "ast.hpp" +#include "ast_fwd_decl.hpp" #include "operation.hpp" namespace Sass { + class Remove_Placeholders : public Operation_CRTP { + + public: - class Remove_Placeholders : public Operation_CRTP { + SelectorList* remove_placeholders(SelectorList*); + void remove_placeholders(SimpleSelector* simple); + void remove_placeholders(CompoundSelector* complex); + void remove_placeholders(ComplexSelector* complex); - void fallback_impl(AST_Node_Ptr n) {} - public: - Selector_List_Ptr remove_placeholders(Selector_List_Ptr); + public: + Remove_Placeholders(); + ~Remove_Placeholders() { } - public: - Remove_Placeholders(); - ~Remove_Placeholders() { } + void operator()(Block*); + void operator()(Ruleset*); + void operator()(CssMediaRule*); + void operator()(Supports_Block*); + void operator()(Directive*); - void operator()(Block_Ptr); - void operator()(Ruleset_Ptr); - void operator()(Media_Block_Ptr); - void operator()(Supports_Block_Ptr); - void operator()(Directive_Ptr); + // ignore missed types + template + void fallback(U x) {} - template - void fallback(U x) { return fallback_impl(x); } - }; + }; } diff --git a/src/libsass/src/sass.cpp b/src/libsass/src/sass.cpp index 72edd7ced..9405c7c73 100644 --- a/src/libsass/src/sass.cpp +++ b/src/libsass/src/sass.cpp @@ -1,4 +1,7 @@ +// sass.hpp must go before all system headers to get the +// __EXTENSIONS__ fix on Solaris. #include "sass.hpp" + #include #include #include @@ -7,6 +10,7 @@ #include "sass.h" #include "file.hpp" #include "util.hpp" +#include "context.hpp" #include "sass_context.hpp" #include "sass_functions.hpp" @@ -148,4 +152,4 @@ namespace Sass { return sass_copy_c_string(str.c_str()); } -} \ No newline at end of file +} diff --git a/src/libsass/src/sass.hpp b/src/libsass/src/sass.hpp index f0550490c..1598d954a 100644 --- a/src/libsass/src/sass.hpp +++ b/src/libsass/src/sass.hpp @@ -14,11 +14,17 @@ // aplies to MSVC and MinGW #ifdef _WIN32 // we do not want the ERROR macro -# define NOGDI +# ifndef NOGDI +# define NOGDI +# endif // we do not want the min/max macro -# define NOMINMAX +# ifndef NOMINMAX +# define NOMINMAX +# endif // we do not want the IN/OUT macro -# define _NO_W32_PSEUDO_MODIFIERS +# ifndef _NO_W32_PSEUDO_MODIFIERS +# define _NO_W32_PSEUDO_MODIFIERS +# endif #endif @@ -59,6 +65,7 @@ namespace Sass { // only used internal to trigger ruby inspect behavior const static Sass_Output_Style INSPECT = SASS_STYLE_INSPECT; const static Sass_Output_Style TO_SASS = SASS_STYLE_TO_SASS; + const static Sass_Output_Style TO_CSS = SASS_STYLE_TO_CSS; // helper to aid dreaded MSVC debug mode // see implementation for more details @@ -90,13 +97,10 @@ struct Sass_Inspect_Options { // Precision for fractional numbers int precision; - // Do not compress colors in selectors - bool in_selector; - // initialization list (constructor with defaults) Sass_Inspect_Options(Sass_Output_Style style = Sass::NESTED, - int precision = 5, bool in_selector = false) - : output_style(style), precision(precision), in_selector(in_selector) + int precision = 10) + : output_style(style), precision(precision) { } }; @@ -125,7 +129,7 @@ struct Sass_Output_Options : Sass_Inspect_Options { // initialization list (constructor with defaults) Sass_Output_Options(Sass_Output_Style style = Sass::NESTED, - int precision = 5, + int precision = 10, const char* indent = " ", const char* linefeed = "\n", bool source_comments = false) diff --git a/src/libsass/src/sass2scss.cpp b/src/libsass/src/sass2scss.cpp index 56333b38e..6ba51be28 100644 --- a/src/libsass/src/sass2scss.cpp +++ b/src/libsass/src/sass2scss.cpp @@ -154,6 +154,21 @@ namespace Sass } + 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) @@ -452,7 +467,7 @@ namespace Sass // right trim input sass = rtrim(sass); - // get postion of first meaningfull character in string + // 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 @@ -542,7 +557,7 @@ namespace Sass // default to a selector // change back if property found converter.selector = true; - // get postion of first whitespace char + // 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) @@ -565,7 +580,7 @@ namespace Sass // assertion for valid result if (pos_colon != std::string::npos) { - // ... after the first word (skip begining colons) + // ... 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; @@ -587,6 +602,7 @@ namespace Sass 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); } @@ -606,23 +622,38 @@ namespace Sass { // get positions for the actual import url size_t pos_import = sass.find_first_of(SASS2SCSS_FIND_WHITESPACE, pos_left + 7); - size_t pos_quote = sass.find_first_not_of(SASS2SCSS_FIND_WHITESPACE, pos_import); - // leave proper urls untouched - if (sass.substr(pos_quote, 4) != "url(") - { - // check if the url appears to be already quoted - if (sass.substr(pos_quote, 1) != "\"" && sass.substr(pos_quote, 1) != "\'") - { - // get position of the last char on the line - size_t pos_end = sass.find_last_not_of(SASS2SCSS_FIND_WHITESPACE); - // assertion check for valid result - if (pos_end != std::string::npos) - { - // add quotes around the full line after the import statement - sass = sass.substr(0, pos_quote) + "\"" + sass.substr(pos_quote, pos_end - pos_quote + 1) + "\""; + 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 ( @@ -727,7 +758,7 @@ namespace Sass scss += flush(sass, converter); } - // get postion of last meaningfull char + // get position of last meaningfull char size_t pos_right = sass.find_last_not_of(SASS2SCSS_FIND_WHITESPACE); // check for invalid result diff --git a/src/libsass/src/sass_context.cpp b/src/libsass/src/sass_context.cpp index afadc66e1..2a354357d 100644 --- a/src/libsass/src/sass_context.cpp +++ b/src/libsass/src/sass_context.cpp @@ -1,20 +1,10 @@ +// sass.hpp must go before all system headers to get the +// __EXTENSIONS__ fix on Solaris. #include "sass.hpp" -#include -#include -#include -#include -#include - -#include "sass.h" #include "ast.hpp" -#include "file.hpp" -#include "json.hpp" -#include "util.hpp" -#include "context.hpp" -#include "sass_context.hpp" + #include "sass_functions.hpp" -#include "ast_fwd_decl.hpp" -#include "error_handling.hpp" +#include "json.hpp" #define LFEED "\n" @@ -28,6 +18,24 @@ namespace Sass { return json_mkstring(str.c_str()); } + static void handle_string_error(Sass_Context* c_ctx, const std::string& msg, int severety) + { + std::stringstream msg_stream; + JsonNode* json_err = json_mkobject(); + msg_stream << "Internal Error: " << msg << std::endl; + json_append_member(json_err, "status", json_mknumber(severety)); + json_append_member(json_err, "message", json_mkstring(msg.c_str())); + json_append_member(json_err, "formatted", json_mkstream(msg_stream)); + try { c_ctx->error_json = json_stringify(json_err, " "); } + catch (...) {} + c_ctx->error_message = sass_copy_string(msg_stream.str()); + c_ctx->error_text = sass_copy_c_string(msg.c_str()); + c_ctx->error_status = severety; + c_ctx->output_string = 0; + c_ctx->source_map_string = 0; + json_delete(json_err); + } + static int handle_error(Sass_Context* c_ctx) { try { throw; @@ -67,23 +75,23 @@ namespace Sass { } // now create the code trace (ToDo: maybe have util functions?) - if (e.pstate.line != std::string::npos && e.pstate.column != std::string::npos) { + if (e.pstate.line != std::string::npos && + e.pstate.column != std::string::npos && + e.pstate.src != nullptr) { size_t lines = e.pstate.line; - const char* line_beg = e.pstate.src; // scan through src until target line // move line_beg pointer to line start - while (line_beg && *line_beg && lines != 0) { + const char* line_beg; + for (line_beg = e.pstate.src; *line_beg != '\0'; ++line_beg) { + if (lines == 0) break; if (*line_beg == '\n') --lines; - utf8::unchecked::next(line_beg); } - const char* line_end = line_beg; // move line_end before next newline character - while (line_end && *line_end && *line_end != '\n') { - if (*line_end == '\n') break; - if (*line_end == '\r') break; - utf8::unchecked::next(line_end); + 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 && *line_end != 0) ++ line_end; + 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; @@ -107,96 +115,34 @@ namespace Sass { json_append_member(json_err, "message", json_mkstring(e.what())); json_append_member(json_err, "formatted", json_mkstream(msg_stream)); try { c_ctx->error_json = json_stringify(json_err, " "); } - catch (...) {} + catch (...) {} // silently ignore this error? c_ctx->error_message = sass_copy_string(msg_stream.str()); c_ctx->error_text = sass_copy_c_string(e.what()); c_ctx->error_status = 1; c_ctx->error_file = sass_copy_c_string(e.pstate.path); c_ctx->error_line = e.pstate.line + 1; c_ctx->error_column = e.pstate.column + 1; - c_ctx->error_src = e.pstate.src; + c_ctx->error_src = sass_copy_c_string(e.pstate.src); c_ctx->output_string = 0; c_ctx->source_map_string = 0; json_delete(json_err); } catch (std::bad_alloc& ba) { std::stringstream msg_stream; - JsonNode* json_err = json_mkobject(); - msg_stream << "Unable to allocate memory: " << ba.what() << std::endl; - json_append_member(json_err, "status", json_mknumber(2)); - json_append_member(json_err, "message", json_mkstring(ba.what())); - json_append_member(json_err, "formatted", json_mkstream(msg_stream)); - try { c_ctx->error_json = json_stringify(json_err, " "); } - catch (...) {} - c_ctx->error_message = sass_copy_string(msg_stream.str()); - c_ctx->error_text = sass_copy_c_string(ba.what()); - c_ctx->error_status = 2; - c_ctx->output_string = 0; - c_ctx->source_map_string = 0; - json_delete(json_err); + msg_stream << "Unable to allocate memory: " << ba.what(); + handle_string_error(c_ctx, msg_stream.str(), 2); } catch (std::exception& e) { - std::stringstream msg_stream; - JsonNode* json_err = json_mkobject(); - msg_stream << "Internal Error: " << e.what() << std::endl; - json_append_member(json_err, "status", json_mknumber(3)); - json_append_member(json_err, "message", json_mkstring(e.what())); - json_append_member(json_err, "formatted", json_mkstream(msg_stream)); - try { c_ctx->error_json = json_stringify(json_err, " "); } - catch (...) {} - c_ctx->error_message = sass_copy_string(msg_stream.str()); - c_ctx->error_text = sass_copy_c_string(e.what()); - c_ctx->error_status = 3; - c_ctx->output_string = 0; - c_ctx->source_map_string = 0; - json_delete(json_err); + handle_string_error(c_ctx, e.what(), 3); } catch (std::string& e) { - std::stringstream msg_stream; - JsonNode* json_err = json_mkobject(); - msg_stream << "Internal Error: " << e << std::endl; - json_append_member(json_err, "status", json_mknumber(4)); - json_append_member(json_err, "message", json_mkstring(e.c_str())); - json_append_member(json_err, "formatted", json_mkstream(msg_stream)); - try { c_ctx->error_json = json_stringify(json_err, " "); } - catch (...) {} - c_ctx->error_message = sass_copy_string(msg_stream.str()); - c_ctx->error_text = sass_copy_c_string(e.c_str()); - c_ctx->error_status = 4; - c_ctx->output_string = 0; - c_ctx->source_map_string = 0; - json_delete(json_err); + handle_string_error(c_ctx, e, 4); } catch (const char* e) { - std::stringstream msg_stream; - JsonNode* json_err = json_mkobject(); - msg_stream << "Internal Error: " << e << std::endl; - json_append_member(json_err, "status", json_mknumber(4)); - json_append_member(json_err, "message", json_mkstring(e)); - json_append_member(json_err, "formatted", json_mkstream(msg_stream)); - try { c_ctx->error_json = json_stringify(json_err, " "); } - catch (...) {} - c_ctx->error_message = sass_copy_string(msg_stream.str()); - c_ctx->error_text = sass_copy_c_string(e); - c_ctx->error_status = 4; - c_ctx->output_string = 0; - c_ctx->source_map_string = 0; - json_delete(json_err); + handle_string_error(c_ctx, e, 4); } catch (...) { - std::stringstream msg_stream; - JsonNode* json_err = json_mkobject(); - msg_stream << "Unknown error occurred" << std::endl; - json_append_member(json_err, "status", json_mknumber(5)); - json_append_member(json_err, "message", json_mkstring("unknown")); - try { c_ctx->error_json = json_stringify(json_err, " "); } - catch (...) {} - c_ctx->error_message = sass_copy_string(msg_stream.str()); - c_ctx->error_text = sass_copy_c_string("unknown"); - c_ctx->error_status = 5; - c_ctx->output_string = 0; - c_ctx->source_map_string = 0; - json_delete(json_err); + handle_string_error(c_ctx, "unknown", 5); } return c_ctx->error_status; } @@ -212,7 +158,7 @@ namespace Sass { { // assert valid pointer - if (compiler == 0) return 0; + 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; @@ -233,7 +179,7 @@ namespace Sass { // dispatch parse call Block_Obj root(cpp_ctx->parse()); // abort on errors - if (!root) return 0; + if (!root) return {}; // skip all prefixed files? (ToDo: check srcmap) // IMO source-maps should point to headers already @@ -253,7 +199,7 @@ namespace Sass { catch (...) { handle_errors(c_ctx); } // error - return 0; + return {}; } @@ -331,8 +277,8 @@ extern "C" { c_ctx->error_message = 0; c_ctx->error_status = 0; // reset error position - c_ctx->error_src = 0; c_ctx->error_file = 0; + c_ctx->error_src = 0; c_ctx->error_line = std::string::npos; c_ctx->error_column = std::string::npos; @@ -381,7 +327,7 @@ extern "C" { inline void init_options (struct Sass_Options* options) { - options->precision = 5; + options->precision = 10; options->indent = " "; options->linefeed = LFEED; } @@ -396,7 +342,9 @@ extern "C" { Sass_File_Context* ADDCALL sass_make_file_context(const char* input_path) { - SharedObj::setTaint(true); // needed for static colors + #ifdef DEBUG_SHARED_PTR + SharedObj::setTaint(true); + #endif struct Sass_File_Context* ctx = (struct Sass_File_Context*) calloc(1, sizeof(struct Sass_File_Context)); if (ctx == 0) { std::cerr << "Error allocating memory for file context" << std::endl; return 0; } ctx->type = SASS_CONTEXT_FILE; @@ -413,6 +361,9 @@ extern "C" { Sass_Data_Context* ADDCALL sass_make_data_context(char* source_string) { + #ifdef DEBUG_SHARED_PTR + SharedObj::setTaint(true); + #endif struct Sass_Data_Context* ctx = (struct Sass_Data_Context*) calloc(1, sizeof(struct Sass_Data_Context)); if (ctx == 0) { std::cerr << "Error allocating memory for data context" << std::endl; return 0; } ctx->type = SASS_CONTEXT_DATA; @@ -448,7 +399,7 @@ extern "C" { 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 usefull (different than with file context) + // 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; } @@ -530,7 +481,7 @@ extern "C" { static void sass_clear_options (struct Sass_Options* options) { if (options == 0) return; - // Deallocate custom functions, headers and importes + // 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); @@ -591,6 +542,7 @@ extern "C" { 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; @@ -599,6 +551,7 @@ extern "C" { 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 @@ -617,7 +570,7 @@ extern "C" { if (cpp_ctx) delete(cpp_ctx); compiler->cpp_ctx = NULL; compiler->c_ctx = NULL; - compiler->root = NULL; + compiler->root = {}; free(compiler); } @@ -653,7 +606,7 @@ extern "C" { 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 conected sass context) + // 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; } @@ -697,9 +650,9 @@ extern "C" { 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*, error_src); IMPLEMENT_SASS_CONTEXT_GETTER(const char*, output_string); IMPLEMENT_SASS_CONTEXT_GETTER(const char*, source_map_string); IMPLEMENT_SASS_CONTEXT_GETTER(char**, included_files); @@ -709,6 +662,7 @@ extern "C" { 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); diff --git a/src/libsass/src/sass_context.hpp b/src/libsass/src/sass_context.hpp index 8ae1fb12c..16c8f16f5 100644 --- a/src/libsass/src/sass_context.hpp +++ b/src/libsass/src/sass_context.hpp @@ -90,7 +90,7 @@ struct Sass_Context : Sass_Options char* error_file; size_t error_line; size_t error_column; - const char* error_src; + char* error_src; // report imported files char** included_files; @@ -126,4 +126,4 @@ struct Sass_Compiler { Sass::Block_Obj root; }; -#endif \ No newline at end of file +#endif diff --git a/src/libsass/src/sass_functions.cpp b/src/libsass/src/sass_functions.cpp index bfbf25838..e576d47c4 100644 --- a/src/libsass/src/sass_functions.cpp +++ b/src/libsass/src/sass_functions.cpp @@ -1,4 +1,7 @@ +// 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" @@ -98,7 +101,7 @@ extern "C" { } // 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 destroyd) + // 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)); @@ -167,21 +170,21 @@ extern "C" { // 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_Ptr ex = Cast((*env->frame)[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_Ptr ex = Cast(env->frame->get_local(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_Ptr ex = Cast(env->frame->get_global(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) { diff --git a/src/libsass/src/sass_functions.hpp b/src/libsass/src/sass_functions.hpp index 3b646d67e..482ed641b 100644 --- a/src/libsass/src/sass_functions.hpp +++ b/src/libsass/src/sass_functions.hpp @@ -3,7 +3,7 @@ #include "sass.h" #include "environment.hpp" -#include "functions.hpp" +#include "fn_utils.hpp" // Struct to hold custom function callback struct Sass_Function { diff --git a/src/libsass/src/sass_util.cpp b/src/libsass/src/sass_util.cpp deleted file mode 100644 index 3aef2bc72..000000000 --- a/src/libsass/src/sass_util.cpp +++ /dev/null @@ -1,149 +0,0 @@ -#include "sass.hpp" -#include "node.hpp" - -namespace Sass { - - - /* - # This is the equivalent of ruby's Sass::Util.paths. - # - # Return an array of all possible paths through the given arrays. - # - # @param arrs [NodeCollection>] - # @return [NodeCollection>] - # - # @example - # paths([[1, 2], [3, 4], [5]]) #=> - # # [[1, 3, 5], - # # [2, 3, 5], - # # [1, 4, 5], - # # [2, 4, 5]] - - The following is the modified version of the ruby code that was more portable to C++. You - should be able to drop it into ruby 3.2.19 and get the same results from ruby sass. - - def paths(arrs) - // I changed the inject and maps to an iterative approach to make it easier to implement in C++ - loopStart = [[]] - - for arr in arrs do - permutations = [] - for e in arr do - for path in loopStart do - permutations.push(path + [e]) - end - end - loopStart = permutations - end - end - */ - Node paths(const Node& arrs) { - - Node loopStart = Node::createCollection(); - loopStart.collection()->push_back(Node::createCollection()); - - for (NodeDeque::iterator arrsIter = arrs.collection()->begin(), arrsEndIter = arrs.collection()->end(); - arrsIter != arrsEndIter; ++arrsIter) { - - Node& arr = *arrsIter; - - Node permutations = Node::createCollection(); - - for (NodeDeque::iterator arrIter = arr.collection()->begin(), arrIterEnd = arr.collection()->end(); - arrIter != arrIterEnd; ++arrIter) { - - Node& e = *arrIter; - - for (NodeDeque::iterator loopStartIter = loopStart.collection()->begin(), loopStartIterEnd = loopStart.collection()->end(); - loopStartIter != loopStartIterEnd; ++loopStartIter) { - - Node& path = *loopStartIter; - - Node newPermutation = Node::createCollection(); - newPermutation.got_line_feed = arr.got_line_feed; - newPermutation.plus(path); - newPermutation.collection()->push_back(e); - - permutations.collection()->push_back(newPermutation); - } - } - - loopStart = permutations; - } - - return loopStart; - } - - - /* - This is the equivalent of ruby sass' Sass::Util.flatten and [].flatten. - Sass::Util.flatten requires the number of levels to flatten, while - [].flatten doesn't and will flatten the entire array. This function - supports both. - - # Flattens the first `n` nested arrays. If n == -1, all arrays will be flattened - # - # @param arr [NodeCollection] The array to flatten - # @param n [int] The number of levels to flatten - # @return [NodeCollection] The flattened array - - The following is the modified version of the ruby code that was more portable to C++. You - should be able to drop it into ruby 3.2.19 and get the same results from ruby sass. - - def flatten(arr, n = -1) - if n != -1 and n == 0 then - return arr - end - - flattened = [] - - for e in arr do - if e.is_a?(Array) then - flattened.concat(flatten(e, n - 1)) - else - flattened << e - end - end - - return flattened - end - */ - Node flatten(Node& arr, int n) { - if (n != -1 && n == 0) { - return arr; - } - - Node flattened = Node::createCollection(); - if (arr.got_line_feed) flattened.got_line_feed = true; - - for (NodeDeque::iterator iter = arr.collection()->begin(), iterEnd = arr.collection()->end(); - iter != iterEnd; iter++) { - Node& e = *iter; - - // e has the lf set - if (e.isCollection()) { - - // e.collection().got_line_feed = e.got_line_feed; - Node recurseFlattened = flatten(e, n - 1); - - if(e.got_line_feed) { - flattened.got_line_feed = e.got_line_feed; - recurseFlattened.got_line_feed = e.got_line_feed; - } - - for(auto i : (*recurseFlattened.collection())) { - if (recurseFlattened.got_line_feed) { - - i.got_line_feed = true; - } - flattened.collection()->push_back(i); - } - - } else { - flattened.collection()->push_back(e); - } - } - - return flattened; - } -} diff --git a/src/libsass/src/sass_util.hpp b/src/libsass/src/sass_util.hpp deleted file mode 100644 index 816da5fd8..000000000 --- a/src/libsass/src/sass_util.hpp +++ /dev/null @@ -1,256 +0,0 @@ -#ifndef SASS_SASS_UTIL_H -#define SASS_SASS_UTIL_H - -#include "ast.hpp" -#include "node.hpp" -#include "debug.hpp" - -namespace Sass { - - - - - /* - This is for ports of functions in the Sass:Util module. - */ - - - /* - # Return a Node collection of all possible paths through the given Node collection of Node collections. - # - # @param arrs [NodeCollection>] - # @return [NodeCollection>] - # - # @example - # paths([[1, 2], [3, 4], [5]]) #=> - # # [[1, 3, 5], - # # [2, 3, 5], - # # [1, 4, 5], - # # [2, 4, 5]] - */ - Node paths(const Node& arrs); - - - /* - This class is a default implementation of a Node comparator that can be passed to the lcs function below. - It uses operator== for equality comparision. It then returns one if the Nodes are equal. - */ - class DefaultLcsComparator { - public: - bool operator()(const Node& one, const Node& two, Node& out) const { - // TODO: Is this the correct C++ interpretation? - // block ||= proc {|a, b| a == b && a} - if (one == two) { - out = one; - return true; - } - - return false; - } - }; - - - typedef std::vector > LCSTable; - - - /* - This is the equivalent of ruby's Sass::Util.lcs_backtrace. - - # Computes a single longest common subsequence for arrays x and y. - # Algorithm from http://en.wikipedia.org/wiki/Longest_common_subsequence_problem#Reading_out_an_LCS - */ - template - Node lcs_backtrace(const LCSTable& c, const Node& x, const Node& y, int i, int j, const ComparatorType& comparator) { - DEBUG_PRINTLN(LCS, "LCSBACK: X=" << x << " Y=" << y << " I=" << i << " J=" << j) - - if (i == 0 || j == 0) { - DEBUG_PRINTLN(LCS, "RETURNING EMPTY") - return Node::createCollection(); - } - - NodeDeque& xChildren = *(x.collection()); - NodeDeque& yChildren = *(y.collection()); - - Node compareOut = Node::createNil(); - if (comparator(xChildren[i], yChildren[j], compareOut)) { - DEBUG_PRINTLN(LCS, "RETURNING AFTER ELEM COMPARE") - Node result = lcs_backtrace(c, x, y, i - 1, j - 1, comparator); - result.collection()->push_back(compareOut); - return result; - } - - if (c[i][j - 1] > c[i - 1][j]) { - DEBUG_PRINTLN(LCS, "RETURNING AFTER TABLE COMPARE") - return lcs_backtrace(c, x, y, i, j - 1, comparator); - } - - DEBUG_PRINTLN(LCS, "FINAL RETURN") - return lcs_backtrace(c, x, y, i - 1, j, comparator); - } - - - /* - This is the equivalent of ruby's Sass::Util.lcs_table. - - # Calculates the memoization table for the Least Common Subsequence algorithm. - # Algorithm from http://en.wikipedia.org/wiki/Longest_common_subsequence_problem#Computing_the_length_of_the_LCS - */ - template - void lcs_table(const Node& x, const Node& y, const ComparatorType& comparator, LCSTable& out) { - DEBUG_PRINTLN(LCS, "LCSTABLE: X=" << x << " Y=" << y) - - NodeDeque& xChildren = *(x.collection()); - NodeDeque& yChildren = *(y.collection()); - - LCSTable c(xChildren.size(), std::vector(yChildren.size())); - - // These shouldn't be necessary since the vector will be initialized to 0 already. - // x.size.times {|i| c[i][0] = 0} - // y.size.times {|j| c[0][j] = 0} - - for (size_t i = 1; i < xChildren.size(); i++) { - for (size_t j = 1; j < yChildren.size(); j++) { - Node compareOut = Node::createNil(); - - if (comparator(xChildren[i], yChildren[j], compareOut)) { - c[i][j] = c[i - 1][j - 1] + 1; - } else { - c[i][j] = std::max(c[i][j - 1], c[i - 1][j]); - } - } - } - - out = c; - } - - - /* - This is the equivalent of ruby's Sass::Util.lcs. - - # Computes a single longest common subsequence for `x` and `y`. - # If there are more than one longest common subsequences, - # the one returned is that which starts first in `x`. - - # @param x [NodeCollection] - # @param y [NodeCollection] - # @comparator An equality check between elements of `x` and `y`. - # @return [NodeCollection] The LCS - - http://en.wikipedia.org/wiki/Longest_common_subsequence_problem - */ - template - Node lcs(Node& x, Node& y, const ComparatorType& comparator) { - DEBUG_PRINTLN(LCS, "LCS: X=" << x << " Y=" << y) - - Node newX = Node::createCollection(); - newX.collection()->push_back(Node::createNil()); - newX.plus(x); - - Node newY = Node::createCollection(); - newY.collection()->push_back(Node::createNil()); - newY.plus(y); - - LCSTable table; - lcs_table(newX, newY, comparator, table); - - return lcs_backtrace(table, newX, newY, static_cast(newX.collection()->size()) - 1, static_cast(newY.collection()->size()) - 1, comparator); - } - - - /* - This is the equivalent of ruby sass' Sass::Util.flatten and [].flatten. - Sass::Util.flatten requires the number of levels to flatten, while - [].flatten doesn't and will flatten the entire array. This function - supports both. - - # Flattens the first `n` nested arrays. If n == -1, all arrays will be flattened - # - # @param arr [NodeCollection] The array to flatten - # @param n [int] The number of levels to flatten - # @return [NodeCollection] The flattened array - */ - Node flatten(Node& arr, int n = -1); - - - /* - This is the equivalent of ruby's Sass::Util.group_by_to_a. - - # Performs the equivalent of `enum.group_by.to_a`, but with a guaranteed - # order. Unlike [#hash_to_a], the resulting order isn't sorted key order; - # instead, it's the same order as `#group_by` has under Ruby 1.9 (key - # appearance order). - # - # @param enum [Enumerable] - # @return [Array<[Object, Array]>] An array of pairs. - - TODO: update @param and @return once I know what those are. - - The following is the modified version of the ruby code that was more portable to C++. You - should be able to drop it into ruby 3.2.19 and get the same results from ruby sass. - - def group_by_to_a(enum, &block) - order = {} - - arr = [] - - grouped = {} - - for e in enum do - key = block[e] - unless order.include?(key) - order[key] = order.size - end - - if not grouped.has_key?(key) then - grouped[key] = [e] - else - grouped[key].push(e) - end - end - - grouped.each do |key, vals| - arr[order[key]] = [key, vals] - end - - arr - end - - */ - template - void group_by_to_a(std::vector& enumeration, KeyFunctorType& keyFunc, std::vector > >& arr /*out*/) { - - std::map order; - - std::map > grouped; - - for (typename std::vector::iterator enumIter = enumeration.begin(), enumIterEnd = enumeration.end(); enumIter != enumIterEnd; enumIter++) { - EnumType& e = *enumIter; - - KeyType key = keyFunc(e); - - if (grouped.find(key->hash()) == grouped.end()) { - order.insert(std::make_pair((unsigned int)order.size(), key)); - - std::vector newCollection; - newCollection.push_back(e); - grouped.insert(std::make_pair(key->hash(), newCollection)); - } else { - std::vector& collection = grouped.at(key->hash()); - collection.push_back(e); - } - } - - for (unsigned int index = 0; index < order.size(); index++) { - KeyType& key = order.at(index); - std::vector& values = grouped.at(key->hash()); - - std::pair > grouping = std::make_pair(key, values); - - arr.push_back(grouping); - } - } - - -} - -#endif diff --git a/src/libsass/src/sass_values.cpp b/src/libsass/src/sass_values.cpp index 34c591a24..518087255 100644 --- a/src/libsass/src/sass_values.cpp +++ b/src/libsass/src/sass_values.cpp @@ -1,9 +1,11 @@ +// 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 "values.hpp" #include "operators.hpp" #include "sass/values.h" #include "sass_values.hpp" @@ -290,7 +292,7 @@ extern "C" { union Sass_Value* ADDCALL sass_value_op (enum Sass_OP op, const union Sass_Value* a, const union Sass_Value* b) { - Sass::Value_Ptr rv; + Sass::Value_Obj rv; try { @@ -312,28 +314,34 @@ extern "C" { } if (sass_value_is_number(a) && sass_value_is_number(b)) { - Number_Ptr_Const l_n = Cast(lhs); - Number_Ptr_Const r_n = Cast(rhs); + 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)) { - Number_Ptr_Const l_n = Cast(lhs); - Color_Ptr_Const r_c = Cast(rhs); + 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)) { - Color_Ptr_Const l_c = Cast(lhs); - Number_Ptr_Const r_n = Cast(rhs); + // 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)) { - Color_Ptr_Const l_c = Cast(lhs); - Color_Ptr_Const r_c = Cast(rhs); + // 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 */ { - Value_Ptr l_v = Cast(lhs); - Value_Ptr r_v = Cast(rhs); + Value* l_v = Cast(lhs); + Value* r_v = Cast(rhs); rv = Operators::op_strings(op, *l_v, *r_v, options, l_v->pstate()); } @@ -341,8 +349,7 @@ extern "C" { if (!rv) return sass_make_error("invalid return value"); // convert result back to ast node - return ast_node_to_sass_value(rv); - + return ast_node_to_sass_value(rv.ptr()); } // simply pass the error message back to the caller for now diff --git a/src/libsass/src/source_map.cpp b/src/libsass/src/source_map.cpp index c171a3f68..74f929bdb 100644 --- a/src/libsass/src/source_map.cpp +++ b/src/libsass/src/source_map.cpp @@ -1,4 +1,7 @@ +// sass.hpp must go before all system headers to get the +// __EXTENSIONS__ fix on Solaris. #include "sass.hpp" + #include #include #include @@ -170,12 +173,12 @@ namespace Sass { current_position += offset; } - void SourceMap::add_open_mapping(const AST_Node_Ptr node) + void SourceMap::add_open_mapping(const AST_Node* node) { mappings.push_back(Mapping(node->pstate(), current_position)); } - void SourceMap::add_close_mapping(const AST_Node_Ptr node) + void SourceMap::add_close_mapping(const AST_Node* node) { mappings.push_back(Mapping(node->pstate() + node->pstate().offset, current_position)); } diff --git a/src/libsass/src/source_map.hpp b/src/libsass/src/source_map.hpp index 07785640f..d6379aa0d 100644 --- a/src/libsass/src/source_map.hpp +++ b/src/libsass/src/source_map.hpp @@ -28,8 +28,8 @@ namespace Sass { void prepend(const Offset& offset); void append(const OutputBuffer& out); void prepend(const OutputBuffer& out); - void add_open_mapping(const AST_Node_Ptr node); - void add_close_mapping(const AST_Node_Ptr node); + void add_open_mapping(const AST_Node* node); + void add_close_mapping(const AST_Node* node); std::string render_srcmap(Context &ctx); ParserState remap(const ParserState& pstate); diff --git a/src/libsass/src/stylesheet.cpp b/src/libsass/src/stylesheet.cpp new file mode 100644 index 000000000..e0e4345c2 --- /dev/null +++ b/src/libsass/src/stylesheet.cpp @@ -0,0 +1,22 @@ +// sass.hpp must go before all system headers to get the +// __EXTENSIONS__ fix on Solaris. +#include "sass.hpp" + +#include "stylesheet.hpp" + +namespace Sass { + + // Constructor + Sass::StyleSheet::StyleSheet(const Resource& res, Block_Obj root) : + Resource(res), + root(root) + { + } + + StyleSheet::StyleSheet(const StyleSheet& sheet) : + Resource(sheet), + root(sheet.root) + { + } + +} diff --git a/src/libsass/src/stylesheet.hpp b/src/libsass/src/stylesheet.hpp new file mode 100644 index 000000000..37e85772a --- /dev/null +++ b/src/libsass/src/stylesheet.hpp @@ -0,0 +1,57 @@ +#ifndef SASS_STYLESHEET_H +#define SASS_STYLESHEET_H + +// sass.hpp must go before all system headers to get the +// __EXTENSIONS__ fix on Solaris. +#include "sass.hpp" + +#include "ast_fwd_decl.hpp" +#include "extender.hpp" +#include "file.hpp" + +namespace Sass { + + // parsed stylesheet from loaded resource + // this should be a `Module` for sass 4.0 + class StyleSheet : public Resource { + public: + + // The canonical URL for this module's source file. This may be `null` + // if the module was loaded from a string without a URL provided. + // Uri get url; + + // Modules that this module uses. + // List get upstream; + + // The module's variables. + // Map get variables; + + // The module's functions. Implementations must ensure + // that each [Callable] is stored under its own name. + // Map get functions; + + // The module's mixins. Implementations must ensure that + // each [Callable] is stored under its own name. + // Map get mixins; + + // The extensions defined in this module, which is also able to update + // [css]'s style rules in-place based on downstream extensions. + // Extender extender; + + // The module's CSS tree. + Block_Obj root; + + public: + + // default argument constructor + StyleSheet(const Resource& res, Block_Obj root); + + // Copy constructor + StyleSheet(const StyleSheet& res); + + }; + + +} + +#endif diff --git a/src/libsass/src/subset_map.cpp b/src/libsass/src/subset_map.cpp deleted file mode 100644 index 24513e498..000000000 --- a/src/libsass/src/subset_map.cpp +++ /dev/null @@ -1,55 +0,0 @@ -#include "sass.hpp" -#include "ast.hpp" -#include "subset_map.hpp" - -namespace Sass { - - void Subset_Map::put(const Compound_Selector_Obj& sel, const SubSetMapPair& value) - { - if (sel->empty()) throw std::runtime_error("internal error: subset map keys may not be empty"); - size_t index = values_.size(); - values_.push_back(value); - for (size_t i = 0, S = sel->length(); i < S; ++i) - { - hash_[(*sel)[i]].push_back(std::make_pair(sel, index)); - } - } - - std::vector Subset_Map::get_kv(const Compound_Selector_Obj& sel) - { - SimpleSelectorDict dict(sel->begin(), sel->end()); // XXX Set - std::vector indices; - for (size_t i = 0, S = sel->length(); i < S; ++i) { - if (!hash_.count((*sel)[i])) { - continue; - } - const std::vector >& subsets = hash_[(*sel)[i]]; - for (const std::pair& item : subsets) { - bool include = true; - for (const Simple_Selector_Obj& it : item.first->elements()) { - auto found = dict.find(it); - if (found == dict.end()) { - include = false; - break; - } - } - if (include) indices.push_back(item.second); - } - } - sort(indices.begin(), indices.end()); - std::vector::iterator indices_end = unique(indices.begin(), indices.end()); - indices.resize(distance(indices.begin(), indices_end)); - - std::vector results; - for (size_t i = 0, S = indices.size(); i < S; ++i) { - results.push_back(values_[indices[i]]); - } - return results; - } - - std::vector Subset_Map::get_v(const Compound_Selector_Obj& sel) - { - return get_kv(sel); - } - -} \ No newline at end of file diff --git a/src/libsass/src/subset_map.hpp b/src/libsass/src/subset_map.hpp deleted file mode 100644 index 5c091e685..000000000 --- a/src/libsass/src/subset_map.hpp +++ /dev/null @@ -1,76 +0,0 @@ -#ifndef SASS_SUBSET_MAP_H -#define SASS_SUBSET_MAP_H - -#include -#include -#include -#include -#include - -#include "ast_fwd_decl.hpp" - - -// #include -// #include -// template -// std::string vector_to_string(std::vector v) -// { -// std::stringstream buffer; -// buffer << "["; - -// if (!v.empty()) -// { buffer << v[0]; } -// else -// { buffer << "]"; } - -// if (v.size() == 1) -// { buffer << "]"; } -// else -// { -// for (size_t i = 1, S = v.size(); i < S; ++i) buffer << ", " << v[i]; -// buffer << "]"; -// } - -// return buffer.str(); -// } - -// template -// std::string set_to_string(set v) -// { -// std::stringstream buffer; -// buffer << "["; -// typename std::set::iterator i = v.begin(); -// if (!v.empty()) -// { buffer << *i; } -// else -// { buffer << "]"; } - -// if (v.size() == 1) -// { buffer << "]"; } -// else -// { -// for (++i; i != v.end(); ++i) buffer << ", " << *i; -// buffer << "]"; -// } - -// return buffer.str(); -// } - -namespace Sass { - - class Subset_Map { - private: - std::vector values_; - std::map >, OrderNodes > hash_; - public: - void put(const Compound_Selector_Obj& sel, const SubSetMapPair& value); - std::vector get_kv(const Compound_Selector_Obj& s); - std::vector get_v(const Compound_Selector_Obj& s); - bool empty() { return values_.empty(); } - void clear() { values_.clear(); hash_.clear(); } - const std::vector values(void) { return values_; } - }; - -} - -#endif diff --git a/src/libsass/src/to_c.hpp b/src/libsass/src/to_c.hpp deleted file mode 100644 index a5331e3bf..000000000 --- a/src/libsass/src/to_c.hpp +++ /dev/null @@ -1,39 +0,0 @@ -#ifndef SASS_TO_C_H -#define SASS_TO_C_H - -#include "ast_fwd_decl.hpp" -#include "operation.hpp" -#include "sass/values.h" - -namespace Sass { - - class To_C : public Operation_CRTP { - // override this to define a catch-all - union Sass_Value* fallback_impl(AST_Node_Ptr n); - - public: - - To_C() { } - ~To_C() { } - - union Sass_Value* operator()(Boolean_Ptr); - union Sass_Value* operator()(Number_Ptr); - union Sass_Value* operator()(Color_Ptr); - union Sass_Value* operator()(String_Constant_Ptr); - union Sass_Value* operator()(String_Quoted_Ptr); - union Sass_Value* operator()(Custom_Warning_Ptr); - union Sass_Value* operator()(Custom_Error_Ptr); - union Sass_Value* operator()(List_Ptr); - union Sass_Value* operator()(Map_Ptr); - union Sass_Value* operator()(Null_Ptr); - union Sass_Value* operator()(Arguments_Ptr); - union Sass_Value* operator()(Argument_Ptr); - - // dispatch to fallback implementation - union Sass_Value* fallback(AST_Node_Ptr x) - { return fallback_impl(x); } - }; - -} - -#endif diff --git a/src/libsass/src/to_value.cpp b/src/libsass/src/to_value.cpp index 3912c5510..fa2b174d9 100644 --- a/src/libsass/src/to_value.cpp +++ b/src/libsass/src/to_value.cpp @@ -1,60 +1,62 @@ +// 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 { - Value_Ptr To_Value::fallback_impl(AST_Node_Ptr n) - { - // throw a runtime error if this happens - // we want a well defined set of possible nodes - throw std::runtime_error("invalid node for to_value"); - } - // Custom_Error is a valid value - Value_Ptr To_Value::operator()(Custom_Error_Ptr e) + Value* To_Value::operator()(Custom_Error* e) { return e; } // Custom_Warning is a valid value - Value_Ptr To_Value::operator()(Custom_Warning_Ptr w) + Value* To_Value::operator()(Custom_Warning* w) { return w; } // Boolean is a valid value - Value_Ptr To_Value::operator()(Boolean_Ptr b) + Value* To_Value::operator()(Boolean* b) { return b; } // Number is a valid value - Value_Ptr To_Value::operator()(Number_Ptr n) + Value* To_Value::operator()(Number* n) { return n; } // Color is a valid value - Value_Ptr To_Value::operator()(Color_Ptr c) + 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_Ptr To_Value::operator()(String_Constant_Ptr s) + Value* To_Value::operator()(String_Constant* s) { return s; } // String_Quoted is a valid value - Value_Ptr To_Value::operator()(String_Quoted_Ptr s) + Value* To_Value::operator()(String_Quoted* s) { return s; } // List is a valid value - Value_Ptr To_Value::operator()(List_Ptr l) + Value* To_Value::operator()(List* l) { List_Obj ll = SASS_MEMORY_NEW(List, l->pstate(), @@ -69,32 +71,32 @@ namespace Sass { } // Map is a valid value - Value_Ptr To_Value::operator()(Map_Ptr m) + Value* To_Value::operator()(Map* m) { return m; } // Null is a valid value - Value_Ptr To_Value::operator()(Null_Ptr n) + Value* To_Value::operator()(Null* n) { return n; } // Function is a valid value - Value_Ptr To_Value::operator()(Function_Ptr n) + Value* To_Value::operator()(Function* n) { return n; } // Argument returns its value - Value_Ptr To_Value::operator()(Argument_Ptr arg) + Value* To_Value::operator()(Argument* arg) { if (!arg->name().empty()) return 0; return arg->value()->perform(this); } - // Selector_List is converted to a string - Value_Ptr To_Value::operator()(Selector_List_Ptr s) + // SelectorList is converted to a string + Value* To_Value::operator()(SelectorList* s) { return SASS_MEMORY_NEW(String_Quoted, s->pstate(), @@ -102,7 +104,7 @@ namespace Sass { } // Binary_Expression is converted to a string - Value_Ptr To_Value::operator()(Binary_Expression_Ptr s) + Value* To_Value::operator()(Binary_Expression* s) { return SASS_MEMORY_NEW(String_Quoted, s->pstate(), diff --git a/src/libsass/src/to_value.hpp b/src/libsass/src/to_value.hpp index 8f64128c4..736e17def 100644 --- a/src/libsass/src/to_value.hpp +++ b/src/libsass/src/to_value.hpp @@ -7,9 +7,7 @@ namespace Sass { - class To_Value : public Operation_CRTP { - - Value_Ptr fallback_impl(AST_Node_Ptr n); + class To_Value : public Operation_CRTP { private: @@ -21,28 +19,26 @@ namespace Sass { : ctx(ctx) { } ~To_Value() { } - using Operation::operator(); - - Value_Ptr operator()(Argument_Ptr); - Value_Ptr operator()(Boolean_Ptr); - Value_Ptr operator()(Number_Ptr); - Value_Ptr operator()(Color_Ptr); - Value_Ptr operator()(String_Constant_Ptr); - Value_Ptr operator()(String_Quoted_Ptr); - Value_Ptr operator()(Custom_Warning_Ptr); - Value_Ptr operator()(Custom_Error_Ptr); - Value_Ptr operator()(List_Ptr); - Value_Ptr operator()(Map_Ptr); - Value_Ptr operator()(Null_Ptr); - Value_Ptr operator()(Function_Ptr); + 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_Ptr operator()(Selector_List_Ptr); - Value_Ptr operator()(Binary_Expression_Ptr); + Value* operator()(SelectorList*); + Value* operator()(Binary_Expression*); - // fallback throws error - template - Value_Ptr fallback(U x) { return fallback_impl(x); } }; } diff --git a/src/libsass/src/units.cpp b/src/libsass/src/units.cpp index 779f1d2b4..cff1682f3 100644 --- a/src/libsass/src/units.cpp +++ b/src/libsass/src/units.cpp @@ -1,5 +1,7 @@ #include "sass.hpp" +#include #include +#include #include "units.hpp" #include "error_handling.hpp" @@ -266,6 +268,10 @@ namespace Sass { return (numerators == rhs.numerators) && (denominators == rhs.denominators); } + bool Units::operator!= (const Units& rhs) const + { + return ! (*this == rhs); + } double Units::normalize() { @@ -324,7 +330,7 @@ namespace Sass { // has the advantage that they will be pre-sorted std::map exponents; - // initialize by summing up occurences in unit vectors + // initialize by summing up occurrences in unit vectors // this will already cancel out equivalent units (e.q. px/px) for (size_t i = 0; i < iL; i ++) exponents[numerators[i]] += 1; for (size_t n = 0; n < nL; n ++) exponents[denominators[n]] -= 1; @@ -390,7 +396,7 @@ namespace Sass { denominators.size() == 0; } - // this does not cover all cases (multiple prefered units) + // this does not cover all cases (multiple preferred units) double Units::convert_factor(const Units& r) const { @@ -461,7 +467,7 @@ namespace Sass { { // get and increment afterwards const std::string r_den = *(r_den_it); - // get possible converstion factor for units + // get possible conversion factor for units double conversion = conversion_factor(l_den, r_den); // skip incompatible denominator if (conversion == 0) { diff --git a/src/libsass/src/units.hpp b/src/libsass/src/units.hpp index 306f5349b..3eb7369bc 100644 --- a/src/libsass/src/units.hpp +++ b/src/libsass/src/units.hpp @@ -83,6 +83,7 @@ namespace Sass { // compare operations bool operator< (const Units& rhs) const; bool operator== (const Units& rhs) const; + bool operator!= (const Units& rhs) const; // factor to convert into given units double convert_factor(const Units&) const; }; diff --git a/src/libsass/src/utf8/checked.h b/src/libsass/src/utf8/checked.h index 693aee964..ce9d27c8a 100644 --- a/src/libsass/src/utf8/checked.h +++ b/src/libsass/src/utf8/checked.h @@ -1,4 +1,4 @@ -// Copyright 2006 Nemanja Trifunovic +// Copyright 2006-2016 Nemanja Trifunovic /* Permission is hereby granted, free of charge, to any person or organization @@ -41,7 +41,7 @@ namespace utf8 class invalid_code_point : public exception { uint32_t cp; public: - invalid_code_point(uint32_t cp) : cp(cp) {} + invalid_code_point(uint32_t codepoint) : cp(codepoint) {} virtual const char* what() const throw() { return "Invalid code point"; } uint32_t code_point() const {return cp;} }; @@ -107,7 +107,9 @@ namespace utf8 *out++ = *it; break; case internal::NOT_ENOUGH_ROOM: - throw not_enough_room(); + out = utf8::append (replacement, out); + start = end; + break; case internal::INVALID_LEAD: out = utf8::append (replacement, out); ++start; @@ -194,10 +196,10 @@ namespace utf8 } template - void retreat (octet_iterator& it, distance_type n, octet_iterator start) + void retreat (octet_iterator& it, distance_type n, octet_iterator end) { for (distance_type i = 0; i < n; ++i) - utf8::prior(it, start); + utf8::prior(it, end); } template @@ -240,7 +242,7 @@ namespace utf8 template u16bit_iterator utf8to16 (octet_iterator start, octet_iterator end, u16bit_iterator result) { - while (start != end) { + while (start < end) { uint32_t cp = utf8::next(start, end); if (cp > 0xffff) { //make a surrogate pair *result++ = static_cast((cp >> 10) + internal::LEAD_OFFSET); @@ -264,7 +266,7 @@ namespace utf8 template u32bit_iterator utf8to32 (octet_iterator start, octet_iterator end, u32bit_iterator result) { - while (start != end) + while (start < end) (*result++) = utf8::next(start, end); return result; @@ -279,9 +281,9 @@ namespace utf8 public: iterator () {} explicit iterator (const octet_iterator& octet_it, - const octet_iterator& range_start, - const octet_iterator& range_end) : - it(octet_it), range_start(range_start), range_end(range_end) + const octet_iterator& rangestart, + const octet_iterator& rangeend) : + it(octet_it), range_start(rangestart), range_end(rangeend) { if (it < range_start || it > range_end) throw std::out_of_range("Invalid utf-8 iterator position"); diff --git a/src/libsass/src/utf8/core.h b/src/libsass/src/utf8/core.h index f85081f8f..927942d92 100644 --- a/src/libsass/src/utf8/core.h +++ b/src/libsass/src/utf8/core.h @@ -222,6 +222,9 @@ namespace internal template utf_error validate_next(octet_iterator& it, octet_iterator end, uint32_t& code_point) { + if (it == end) + return NOT_ENOUGH_ROOM; + // Save the original value of it so we can go back in case of failure // Of course, it does not make much sense with i.e. stream iterators octet_iterator original_it = it; diff --git a/src/libsass/src/utf8_string.cpp b/src/libsass/src/utf8_string.cpp index 19425521c..dc61f733f 100644 --- a/src/libsass/src/utf8_string.cpp +++ b/src/libsass/src/utf8_string.cpp @@ -1,4 +1,7 @@ +// sass.hpp must go before all system headers to get the +// __EXTENSIONS__ fix on Solaris. #include "sass.hpp" + #include #include #include diff --git a/src/libsass/src/util.cpp b/src/libsass/src/util.cpp index 60f69ab76..272cc9669 100644 --- a/src/libsass/src/util.cpp +++ b/src/libsass/src/util.cpp @@ -2,6 +2,7 @@ #include "sass.h" #include "ast.hpp" #include "util.hpp" +#include "util_string.hpp" #include "lexer.hpp" #include "prelexer.hpp" #include "constants.hpp" @@ -26,8 +27,8 @@ namespace Sass { #endif // https://github.com/sass/sass/commit/4e3e1d5684cc29073a507578fc977434ff488c93 - if (fmod(val, 1) - 0.5 > - std::pow(0.1, precision + 1)) return std::ceil(val); - else if (fmod(val, 1) - 0.5 > std::pow(0.1, precision)) return std::floor(val); + if (std::fmod(val, 1) - 0.5 > - std::pow(0.1, precision + 1)) return std::ceil(val); + else if (std::fmod(val, 1) - 0.5 > std::pow(0.1, precision)) return std::floor(val); // work around some compiler issue // cygwin has it not defined in std using namespace std; @@ -163,42 +164,64 @@ namespace Sass { std::replace(str.begin(), str.end(), '\n', ' '); } - // bell characters are replaced with spaces - // also eats spaces after line-feeds (ltrim) + // 1. Removes whitespace after newlines. + // 2. Replaces newlines with spaces. + // + // This method only considers LF and CRLF as newlines. std::string string_to_output(const std::string& str) { - std::string out(""); - bool lf = false; - for (auto i : str) { - if (i == '\n') { - out += ' '; - lf = true; - } else if (!(lf && isspace(i))) { - out += i; - lf = false; + std::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 == std::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 != std::string::npos) { + pos = non_space; } } - return out; + result.append(str, pos, std::string::npos); + return result; } std::string escape_string(const std::string& str) { - std::string out(""); - for (auto i : str) { - if (i == '\n') { - out += "\\n"; - } else if (i == '\r') { - out += "\\r"; - } else if (i == '\t') { - out += "\\t"; - } else { - out += i; + std::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; } - std::string comment_to_string(const std::string& text) + std::string comment_to_compact_string(const std::string& text) { std::string str = ""; size_t has = 0; @@ -207,7 +230,6 @@ namespace Sass { for (auto i : text) { if (clean) { if (i == '\n') { has = 0; } - else if (i == '\r') { has = 0; } else if (i == '\t') { ++ has; } else if (i == ' ') { ++ has; } else if (i == '*') {} @@ -219,8 +241,6 @@ namespace Sass { } } else if (i == '\n') { clean = true; - } else if (i == '\r') { - clean = true; } else { str += i; } @@ -270,7 +290,7 @@ namespace Sass { // parse as many sequence chars as possible // ToDo: Check if ruby aborts after possible max - while (i + len < L && s[i + len] && isxdigit(s[i + len])) ++ len; + while (i + len < L && s[i + len] && Util::ascii_isxdigit(static_cast(s[i + len]))) ++ len; if (len > 1) { @@ -356,7 +376,7 @@ namespace Sass { // parse as many sequence chars as possible // ToDo: Check if ruby aborts after possible max - while (i + len < L && s[i + len] && isxdigit(s[i + len])) ++ len; + while (i + len < L && s[i + len] && Util::ascii_isxdigit(static_cast(s[i + len]))) ++ len; // hex string? if (keep_utf8_sequences) { @@ -508,42 +528,15 @@ namespace Sass { } namespace Util { - using std::string; - - std::string rtrim(const std::string &str) { - std::string trimmed = str; - size_t pos_ws = trimmed.find_last_not_of(" \t\n\v\f\r"); - if (pos_ws != std::string::npos) - { trimmed.erase(pos_ws + 1); } - else { trimmed.clear(); } - return trimmed; - } - std::string normalize_underscores(const std::string& str) { - std::string normalized = str; - for(size_t i = 0, L = normalized.length(); i < L; ++i) { - if(normalized[i] == '_') { - normalized[i] = '-'; - } - } - return normalized; - } - - std::string normalize_decimals(const std::string& str) { - std::string prefix = "0"; - std::string normalized = str; - - return normalized[0] == '.' ? normalized.insert(0, prefix) : normalized; - } - - bool isPrintable(Ruleset_Ptr r, Sass_Output_Style style) { + bool isPrintable(Ruleset* r, Sass_Output_Style style) { if (r == NULL) { return false; } Block_Obj b = r->block(); - Selector_List_Ptr sl = Cast(r->selector()); + SelectorList* sl = r->selector(); bool hasSelectors = sl ? sl->length() > 0 : false; if (!hasSelectors) { @@ -556,14 +549,14 @@ namespace Sass { Statement_Obj stm = b->at(i); if (Cast(stm)) { return true; - } else if (Declaration_Ptr d = Cast(stm)) { + } else if (Declaration* d = Cast(stm)) { return isPrintable(d, style); - } else if (Has_Block_Ptr p = Cast(stm)) { + } else if (Has_Block* p = Cast(stm)) { Block_Obj pChildBlock = p->block(); if (isPrintable(pChildBlock, style)) { hasPrintableChildBlocks = true; } - } else if (Comment_Ptr c = Cast(stm)) { + } else if (Comment* c = Cast(stm)) { // keep for uncompressed if (style != COMPRESSED) { hasDeclarations = true; @@ -584,17 +577,17 @@ namespace Sass { return false; } - bool isPrintable(String_Constant_Ptr s, Sass_Output_Style style) + bool isPrintable(String_Constant* s, Sass_Output_Style style) { return ! s->value().empty(); } - bool isPrintable(String_Quoted_Ptr s, Sass_Output_Style style) + bool isPrintable(String_Quoted* s, Sass_Output_Style style) { return true; } - bool isPrintable(Declaration_Ptr d, Sass_Output_Style style) + bool isPrintable(Declaration* d, Sass_Output_Style style) { Expression_Obj val = d->value(); if (String_Quoted_Obj sq = Cast(val)) return isPrintable(sq.ptr(), style); @@ -602,7 +595,7 @@ namespace Sass { return true; } - bool isPrintable(Supports_Block_Ptr f, Sass_Output_Style style) { + bool isPrintable(Supports_Block* f, Sass_Output_Style style) { if (f == NULL) { return false; } @@ -616,7 +609,7 @@ namespace Sass { if (Cast(stm) || Cast(stm)) { hasDeclarations = true; } - else if (Has_Block_Ptr b = Cast(stm)) { + else if (Has_Block* b = Cast(stm)) { Block_Obj pChildBlock = b->block(); if (!b->is_invisible()) { if (isPrintable(pChildBlock, style)) { @@ -633,36 +626,37 @@ namespace Sass { return false; } - bool isPrintable(Media_Block_Ptr m, Sass_Output_Style style) + bool isPrintable(CssMediaRule* m, Sass_Output_Style style) { - if (m == 0) return false; + if (m == nullptr) return false; Block_Obj b = m->block(); - if (b == 0) return false; + 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_Ptr c = Cast(stm)) { + else if (Comment* c = Cast(stm)) { if (isPrintable(c, style)) { return true; } } - else if (Ruleset_Ptr r = Cast(stm)) { + else if (Ruleset* r = Cast(stm)) { if (isPrintable(r, style)) { return true; } } - else if (Supports_Block_Ptr f = Cast(stm)) { + else if (Supports_Block* f = Cast(stm)) { if (isPrintable(f, style)) { return true; } } - else if (Media_Block_Ptr mb = Cast(stm)) { + else if (CssMediaRule* mb = Cast(stm)) { if (isPrintable(mb, style)) { return true; } } - else if (Has_Block_Ptr b = Cast(stm)) { + else if (Has_Block* b = Cast(stm)) { if (isPrintable(b->block(), style)) { return true; } @@ -671,7 +665,7 @@ namespace Sass { return false; } - bool isPrintable(Comment_Ptr c, Sass_Output_Style style) + bool isPrintable(Comment* c, Sass_Output_Style style) { // keep for uncompressed if (style != COMPRESSED) { @@ -695,27 +689,27 @@ namespace Sass { if (Cast(stm) || Cast(stm)) { return true; } - else if (Comment_Ptr c = Cast(stm)) { + else if (Comment* c = Cast(stm)) { if (isPrintable(c, style)) { return true; } } - else if (Ruleset_Ptr r = Cast(stm)) { + else if (Ruleset* r = Cast(stm)) { if (isPrintable(r, style)) { return true; } } - else if (Supports_Block_Ptr f = Cast(stm)) { + else if (Supports_Block* f = Cast(stm)) { if (isPrintable(f, style)) { return true; } } - else if (Media_Block_Ptr m = Cast(stm)) { + else if (CssMediaRule * m = Cast(stm)) { if (isPrintable(m, style)) { return true; } } - else if (Has_Block_Ptr b = Cast(stm)) { + else if (Has_Block* b = Cast(stm)) { if (isPrintable(b->block(), style)) { return true; } @@ -725,9 +719,5 @@ namespace Sass { return false; } - bool isAscii(const char chr) { - return unsigned(chr) < 128; - } - } } diff --git a/src/libsass/src/util.hpp b/src/libsass/src/util.hpp index f23475fe0..64e8e4912 100644 --- a/src/libsass/src/util.hpp +++ b/src/libsass/src/util.hpp @@ -1,17 +1,35 @@ #ifndef SASS_UTIL_H #define SASS_UTIL_H -#include -#include -#include +// 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* = ""); @@ -20,7 +38,7 @@ namespace Sass { std::string read_css_string(const std::string& str, bool css = true); std::string evacuate_escapes(const std::string& str); std::string string_to_output(const std::string& str); - std::string comment_to_string(const std::string& text); + std::string comment_to_compact_string(const std::string& text); std::string read_hex_escapes(const std::string& str); std::string escape_string(const std::string& str); void newline_to_space(std::string& str); @@ -34,22 +52,53 @@ namespace Sass { bool peek_linefeed(const char* start); - namespace Util { + // 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 std::string& str, const char* prefix, size_t prefix_len) { + return str.compare(0, prefix_len, prefix) == 0; + } + + inline bool starts_with(const std::string& str, const char* prefix) { + return starts_with(str, prefix, std::strlen(prefix)); + } - std::string rtrim(const std::string& str); + // C++20 `ends_with` equivalent. + // See https://en.cppreference.com/w/cpp/string/basic_string/ends_with + inline bool ends_with(const std::string& str, const std::string& suffix) { + return suffix.size() <= str.size() && std::equal(suffix.rbegin(), suffix.rend(), str.rbegin()); + } + + inline bool ends_with(const std::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; + } - std::string normalize_underscores(const std::string& str); - std::string normalize_decimals(const std::string& str); + inline bool ends_with(const std::string& str, const char* suffix) { + return ends_with(str, suffix, std::strlen(suffix)); + } + + namespace Util { - bool isPrintable(Ruleset_Ptr r, Sass_Output_Style style = NESTED); - bool isPrintable(Supports_Block_Ptr r, Sass_Output_Style style = NESTED); - bool isPrintable(Media_Block_Ptr r, Sass_Output_Style style = NESTED); - bool isPrintable(Comment_Ptr b, Sass_Output_Style style = NESTED); + bool isPrintable(Ruleset* r, Sass_Output_Style style = NESTED); + bool isPrintable(Supports_Block* r, Sass_Output_Style style = NESTED); + bool isPrintable(CssMediaRule* r, Sass_Output_Style style = NESTED); + bool isPrintable(Comment* b, Sass_Output_Style style = NESTED); bool isPrintable(Block_Obj b, Sass_Output_Style style = NESTED); - bool isPrintable(String_Constant_Ptr s, Sass_Output_Style style = NESTED); - bool isPrintable(String_Quoted_Ptr s, Sass_Output_Style style = NESTED); - bool isPrintable(Declaration_Ptr d, Sass_Output_Style style = NESTED); - bool isAscii(const char chr); + 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); } } diff --git a/src/libsass/src/util_string.cpp b/src/libsass/src/util_string.cpp new file mode 100644 index 000000000..cc15d36b6 --- /dev/null +++ b/src/libsass/src/util_string.cpp @@ -0,0 +1,125 @@ +#include "util_string.hpp" + +#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 std::string& test) { + // Work directly on characters + const char* src = test.c_str(); + // There is a small chance that the search string + // Is longer than the rest of the string to look at + while (*lit && (*src == *lit || *src + 32 == *lit)) { + ++src, ++lit; + } + // True if literal is at end + // If not test was too long + return *lit == 0; + } + + void ascii_str_tolower(std::string* s) { + for (auto& ch : *s) { + ch = ascii_tolower(static_cast(ch)); + } + } + + void ascii_str_toupper(std::string* s) { + for (auto& ch : *s) { + ch = ascii_toupper(static_cast(ch)); + } + } + + std::string rtrim(std::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. + // ########################################################################### + std::string unvendor(const std::string& name) + { + if (name.size() < 2) return name; + if (name[0] != '-') return name; + if (name[1] == '-') return name; + for (size_t i = 2; i < name.size(); i++) { + if (name[i] == '-') return name.substr(i + 1); + } + return name; + } + // EO unvendor + + std::string normalize_newlines(const std::string& str) { + std::string result; + result.reserve(str.size()); + std::size_t pos = 0; + while (true) { + const std::size_t newline = str.find_first_of("\n\f\r", pos); + if (newline == std::string::npos) break; + result.append(str, pos, newline - pos); + result += '\n'; + if (str[newline] == '\r' && str[newline + 1] == '\n') { + pos = newline + 2; + } + else { + pos = newline + 1; + } + } + result.append(str, pos, std::string::npos); + return result; + } + + std::string normalize_underscores(const std::string& str) { + std::string normalized = str; + std::replace(normalized.begin(), normalized.end(), '_', '-'); + return normalized; + } + + std::string normalize_decimals(const std::string& str) { + std::string normalized; + if (!str.empty() && str[0] == '.') { + normalized.reserve(str.size() + 1); + normalized += '0'; + normalized += str; + } + else { + normalized = str; + } + return normalized; + } + + char opening_bracket_for(char closing_bracket) { + switch (closing_bracket) { + case ')': return '('; + case ']': return '['; + case '}': return '{'; + default: return '\0'; + } + } + + char closing_bracket_for(char opening_bracket) { + switch (opening_bracket) { + case '(': return ')'; + case '[': return ']'; + case '{': return '}'; + default: return '\0'; + } + } + + } + // namespace Util + +} +// namespace Sass diff --git a/src/libsass/src/util_string.hpp b/src/libsass/src/util_string.hpp new file mode 100644 index 000000000..8aad9328a --- /dev/null +++ b/src/libsass/src/util_string.hpp @@ -0,0 +1,72 @@ +#ifndef SASS_UTIL_STRING_H +#define SASS_UTIL_STRING_H + +#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 std::string& test); + + // ########################################################################### + // Returns [name] without a vendor prefix. + // If [name] has no vendor prefix, it's returned as-is. + // ########################################################################### + std::string unvendor(const std::string& name); + + std::string rtrim(std::string str); + std::string normalize_newlines(const std::string& str); + std::string normalize_underscores(const std::string& str); + std::string normalize_decimals(const std::string& str); + char opening_bracket_for(char closing_bracket); + char closing_bracket_for(char opening_bracket); + + // 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(std::string* s); + + inline char ascii_toupper(unsigned char c) { + if (c >= 'a' && c <= 'z') return c - 32; + return c; + } + + void ascii_str_toupper(std::string* s); + + } // namespace Sass +} // namespace Util +#endif // SASS_UTIL_STRING_H diff --git a/src/libsass/src/values.cpp b/src/libsass/src/values.cpp index 0f2fd48d7..c9a53d80f 100644 --- a/src/libsass/src/values.cpp +++ b/src/libsass/src/values.cpp @@ -1,4 +1,7 @@ +// sass.hpp must go before all system headers to get the +// __EXTENSIONS__ fix on Solaris. #include "sass.hpp" + #include "sass.h" #include "values.hpp" @@ -7,21 +10,26 @@ namespace Sass { // convert value from C++ side to C-API - union Sass_Value* ast_node_to_sass_value (const Expression_Ptr val) + union Sass_Value* ast_node_to_sass_value (const Expression* val) { if (val->concrete_type() == Expression::NUMBER) { - Number_Ptr_Const res = Cast(val); + const Number* res = Cast(val); return sass_make_number(res->value(), res->unit().c_str()); } else if (val->concrete_type() == Expression::COLOR) { - Color_Ptr_Const col = Cast(val); - return sass_make_color(col->r(), col->g(), col->b(), col->a()); + 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) { - List_Ptr_Const l = Cast(val); + 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) { Expression_Obj obj = l->at(i); @@ -32,7 +40,7 @@ namespace Sass { } else if (val->concrete_type() == Expression::MAP) { - Map_Ptr_Const m = Cast(val); + const Map* m = Cast(val); union Sass_Value* map = sass_make_map(m->length()); size_t i = 0; for (Expression_Obj key : m->keys()) { sass_map_set_key(map, i, ast_node_to_sass_value(key)); @@ -47,16 +55,16 @@ namespace Sass { } else if (val->concrete_type() == Expression::BOOLEAN) { - Boolean_Ptr_Const res = Cast(val); + const Boolean* res = Cast(val); return sass_make_boolean(res->value()); } else if (val->concrete_type() == Expression::STRING) { - if (String_Quoted_Ptr_Const qstr = Cast(val)) + if (const String_Quoted* qstr = Cast(val)) { return sass_make_qstring(qstr->value().c_str()); } - else if (String_Constant_Ptr_Const cstr = Cast(val)) + else if (const String_Constant* cstr = Cast(val)) { return sass_make_string(cstr->value().c_str()); } @@ -65,7 +73,7 @@ namespace Sass { } // convert value from C-API to C++ side - Value_Ptr sass_value_to_ast_node (const union Sass_Value* val) + Value* sass_value_to_ast_node (const union Sass_Value* val) { switch (sass_value_get_tag(val)) { case SASS_NUMBER: @@ -78,7 +86,8 @@ namespace Sass { ParserState("[C-VALUE]"), sass_boolean_get_value(val)); case SASS_COLOR: - return SASS_MEMORY_NEW(Color, + // ToDo: allow to also use HSLA colors!! + return SASS_MEMORY_NEW(Color_RGBA, ParserState("[C-VALUE]"), sass_color_get_r(val), sass_color_get_g(val), @@ -94,7 +103,7 @@ namespace Sass { ParserState("[C-VALUE]"), sass_string_get_value(val)); case SASS_LIST: { - List_Ptr l = SASS_MEMORY_NEW(List, + List* l = SASS_MEMORY_NEW(List, ParserState("[C-VALUE]"), sass_list_get_length(val), sass_list_get_separator(val)); @@ -105,7 +114,7 @@ namespace Sass { return l; } case SASS_MAP: { - Map_Ptr m = SASS_MEMORY_NEW(Map, ParserState("[C-VALUE]")); + Map* m = SASS_MEMORY_NEW(Map, ParserState("[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)), diff --git a/src/libsass/src/values.hpp b/src/libsass/src/values.hpp index f78ca1281..3c4c687b0 100644 --- a/src/libsass/src/values.hpp +++ b/src/libsass/src/values.hpp @@ -5,8 +5,8 @@ namespace Sass { - union Sass_Value* ast_node_to_sass_value (const Expression_Ptr val); - Value_Ptr sass_value_to_ast_node (const union Sass_Value* val); + 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/libsass/test/Makefile b/src/libsass/test/Makefile new file mode 100644 index 000000000..f990a43d6 --- /dev/null +++ b/src/libsass/test/Makefile @@ -0,0 +1,24 @@ +CXX ?= c++ +CXXFLAGS := -I ../include/ -std=c++11 -fsanitize=address -g -O1 -fno-omit-frame-pointer + +test: test_shared_ptr test_util_string + +test_shared_ptr: build/test_shared_ptr + @ASAN_OPTIONS="symbolize=1" build/test_shared_ptr + +test_util_string: build/test_util_string + @ASAN_OPTIONS="symbolize=1" build/test_util_string + +build: + @mkdir build + +build/test_shared_ptr: test_shared_ptr.cpp ../src/memory/SharedPtr.cpp | build + $(CXX) $(CXXFLAGS) -o build/test_shared_ptr test_shared_ptr.cpp ../src/memory/SharedPtr.cpp + +build/test_util_string: test_util_string.cpp ../src/util_string.cpp | build + $(CXX) $(CXXFLAGS) -o build/test_util_string test_util_string.cpp ../src/util_string.cpp + +clean: | build + rm -rf build + +.PHONY: test test_shared_ptr test_util_string clean diff --git "a/src/libsass/test/e2e/unicode-pwd/Sa\314\201ss-UT\314\247F8/input.scss" "b/src/libsass/test/e2e/unicode-pwd/Sa\314\201ss-UT\314\247F8/input.scss" new file mode 100644 index 000000000..2d4b28778 --- /dev/null +++ "b/src/libsass/test/e2e/unicode-pwd/Sa\314\201ss-UT\314\247F8/input.scss" @@ -0,0 +1,3 @@ +span.utf8-in-path { + margin: auto; +} diff --git "a/src/libsass/test/e2e/unicode-pwd/Sa\314\201ss-UT\314\247F8/output.css" "b/src/libsass/test/e2e/unicode-pwd/Sa\314\201ss-UT\314\247F8/output.css" new file mode 100644 index 000000000..2d4b28778 --- /dev/null +++ "b/src/libsass/test/e2e/unicode-pwd/Sa\314\201ss-UT\314\247F8/output.css" @@ -0,0 +1,3 @@ +span.utf8-in-path { + margin: auto; +} diff --git "a/src/libsass/test/e2e/unicode-pwd/S\303\241ss-U\305\242F8/input.scss" "b/src/libsass/test/e2e/unicode-pwd/S\303\241ss-U\305\242F8/input.scss" new file mode 100644 index 000000000..2d4b28778 --- /dev/null +++ "b/src/libsass/test/e2e/unicode-pwd/S\303\241ss-U\305\242F8/input.scss" @@ -0,0 +1,3 @@ +span.utf8-in-path { + margin: auto; +} diff --git "a/src/libsass/test/e2e/unicode-pwd/S\303\241ss-U\305\242F8/output.css" "b/src/libsass/test/e2e/unicode-pwd/S\303\241ss-U\305\242F8/output.css" new file mode 100644 index 000000000..2d4b28778 --- /dev/null +++ "b/src/libsass/test/e2e/unicode-pwd/S\303\241ss-U\305\242F8/output.css" @@ -0,0 +1,3 @@ +span.utf8-in-path { + margin: auto; +} diff --git a/src/libsass/test/test_node.cpp b/src/libsass/test/test_node.cpp deleted file mode 100644 index 905dc1899..000000000 --- a/src/libsass/test/test_node.cpp +++ /dev/null @@ -1,94 +0,0 @@ -#include -#include - -#include "node.hpp" -#include "parser.hpp" - - -#define STATIC_ARRAY_SIZE(array) (sizeof((array))/sizeof((array[0]))) - - -namespace Sass { - - Context ctx = Context::Data(); - - const char* const ROUNDTRIP_TESTS[] = { - NULL, - "~", - "CMPD", - "~ CMPD", - "CMPD >", - "> > CMPD", - "CMPD ~ ~", - "> + CMPD1.CMPD2 > ~", - "> + CMPD1.CMPD2 CMPD3.CMPD4 > ~", - "+ CMPD1 CMPD2 ~ CMPD3 + CMPD4 > CMPD5 > ~" - }; - - - - static Complex_Selector* createComplexSelector(std::string src) { - std::string temp(src); - temp += ";"; - return (*Parser::from_c_str(temp.c_str(), ctx, "", Position()).parse_selector_list())[0]; - } - - - void roundtripTest(const char* toTest) { - - // Create the initial selector - - Complex_Selector* pOrigSelector = NULL; - if (toTest) { - pOrigSelector = createComplexSelector(toTest); - } - - std::string expected(pOrigSelector ? pOrigSelector->to_string() : "NULL"); - - - // Roundtrip the selector into a node and back - - Node node = complexSelectorToNode(pOrigSelector, ctx); - - std::stringstream nodeStringStream; - nodeStringStream << node; - std::string nodeString = nodeStringStream.str(); - cout << "ASNODE: " << node << endl; - - Complex_Selector* pNewSelector = nodeToComplexSelector(node, ctx); - - // Show the result - - std::string result(pNewSelector ? pNewSelector->to_string() : "NULL"); - - cout << "SELECTOR: " << expected << endl; - cout << "NEW SELECTOR: " << result << endl; - - - // Test that they are equal using the equality operator - - assert( (!pOrigSelector && !pNewSelector ) || (pOrigSelector && pNewSelector) ); - if (pOrigSelector) { - assert( *pOrigSelector == *pNewSelector ); - } - - - // Test that they are equal by comparing the string versions of the selectors - - assert(expected == result); - - } - - - int main() { - for (int index = 0; index < STATIC_ARRAY_SIZE(ROUNDTRIP_TESTS); index++) { - const char* const toTest = ROUNDTRIP_TESTS[index]; - cout << "\nINPUT STRING: " << (toTest ? toTest : "NULL") << endl; - roundtripTest(toTest); - } - - cout << "\nTesting Done.\n"; - } - - -} diff --git a/src/libsass/test/test_paths.cpp b/src/libsass/test/test_paths.cpp deleted file mode 100644 index bfcf8ec6d..000000000 --- a/src/libsass/test/test_paths.cpp +++ /dev/null @@ -1,28 +0,0 @@ -#include -#include "../paths.hpp" - -using namespace Sass; - -template -std::vector& operator<<(std::vector& v, const T& e) -{ - v.push_back(e); - return v; -} - -int main() -{ - std::vector v1, v2, v3; - v1 << 1 << 2; - v2 << 3; - v3 << 4 << 5 << 6; - - std::vector > ss; - ss << v1 << v2 << v3; - - std::vector > ps = paths(ss); - for (size_t i = 0, S = ps.size(); i < S; ++i) { - std::cout << vector_to_string(ps[i]) << std::endl; - } - return 0; -} diff --git a/src/libsass/test/test_selector_difference.cpp b/src/libsass/test/test_selector_difference.cpp deleted file mode 100644 index e2880c0b0..000000000 --- a/src/libsass/test/test_selector_difference.cpp +++ /dev/null @@ -1,25 +0,0 @@ -#include "../ast.hpp" -#include "../context.hpp" -#include "../parser.hpp" -#include -#include - -using namespace Sass; - -Context ctx = Context::Data(); - -Compound_Selector* selector(std::string src) -{ return Parser::from_c_str(src.c_str(), ctx, "", Position()).parse_compound_selector(); } - -void diff(std::string s, std::string t) -{ - std::cout << s << " - " << t << " = " << selector(s + ";")->minus(selector(t + ";"), ctx)->to_string() << std::endl; -} - -int main() -{ - diff(".a.b.c", ".c.b"); - diff(".a.b.c", ".fludge.b"); - - return 0; -} diff --git a/src/libsass/test/test_shared_ptr.cpp b/src/libsass/test/test_shared_ptr.cpp new file mode 100644 index 000000000..265608827 --- /dev/null +++ b/src/libsass/test/test_shared_ptr.cpp @@ -0,0 +1,190 @@ +#include "../src/memory/SharedPtr.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; } + std::string to_string() const { + std::ostringstream result; + result << "refcount=" << refcount << " destroyed=" << *destroyed_; + return result.str(); + } + private: + bool *destroyed_; +}; + +using SharedTestObj = Sass::SharedImpl; + +bool TestOneSharedPtr() { + bool destroyed = false; + { + SharedTestObj a = new TestObj(&destroyed); + } + ASSERT(destroyed); + return true; +} + +bool TestTwoSharedPtrs() { + bool destroyed = false; + { + SharedTestObj a = new TestObj(&destroyed); + { + SharedTestObj b = a; + } + ASSERT(!destroyed); + } + ASSERT(destroyed); + return true; +} + +bool TestSelfAssignment() { + bool destroyed = false; + { + SharedTestObj a = new TestObj(&destroyed); + a = a; + ASSERT(!destroyed); + } + ASSERT(destroyed); + return true; +} + +bool TestPointerAssignment() { + bool destroyed = false; + std::unique_ptr ptr(new TestObj(&destroyed)); + { + SharedTestObj a = ptr.get(); + } + ASSERT(destroyed); + ptr.release(); + return true; +} + +bool TestOneSharedPtrDetach() { + bool destroyed = false; + std::unique_ptr ptr(new TestObj(&destroyed)); + { + SharedTestObj a = ptr.get(); + a.detach(); + } + ASSERT(!destroyed); + return true; +} + +bool TestTwoSharedPtrsDetach() { + bool destroyed = false; + std::unique_ptr ptr(new TestObj(&destroyed)); + { + SharedTestObj a = ptr.get(); + { + SharedTestObj b = a; + b.detach(); + } + ASSERT(!destroyed); + a.detach(); + } + ASSERT(!destroyed); + return true; +} + +bool TestSelfAssignDetach() { + bool destroyed = false; + std::unique_ptr ptr(new TestObj(&destroyed)); + { + SharedTestObj a = ptr.get(); + a = a.detach(); + ASSERT(!destroyed); + } + ASSERT(destroyed); + ptr.release(); + return true; +} + +bool TestDetachedPtrIsNotDestroyedUntilAssignment() { + bool destroyed = false; + std::unique_ptr ptr(new TestObj(&destroyed)); + { + SharedTestObj a = ptr.get(); + SharedTestObj b = a; + ASSERT(a.detach() == ptr.get()); + ASSERT(!destroyed); + } + ASSERT(!destroyed); + { + SharedTestObj c = ptr.get(); + ASSERT(!destroyed); + } + ASSERT(destroyed); + ptr.release(); + return true; +} + +bool TestDetachNull() { + SharedTestObj a; + ASSERT(a.detach() == nullptr); + return true; +} + +class EmptyTestObj : public Sass::SharedObj { + public: + std::string to_string() const { return ""; } +}; + +bool TestComparisonWithSharedPtr() { + Sass::SharedImpl a = new EmptyTestObj(); + ASSERT(a == a); + Sass::SharedImpl b = a; + ASSERT(a == b); + Sass::SharedImpl c = new EmptyTestObj(); + ASSERT(a != c); + Sass::SharedImpl nullobj; + ASSERT(a != nullobj); + ASSERT(nullobj == nullobj); + return true; +} + +bool TestComparisonWithNullptr() { + Sass::SharedImpl a = new EmptyTestObj(); + ASSERT(a != nullptr); + Sass::SharedImpl nullobj; + ASSERT(nullobj == nullptr); + 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; + TEST(TestOneSharedPtr); + TEST(TestTwoSharedPtrs); + TEST(TestSelfAssignment); + TEST(TestPointerAssignment); + TEST(TestOneSharedPtrDetach); + TEST(TestTwoSharedPtrsDetach); + TEST(TestSelfAssignDetach); + TEST(TestDetachedPtrIsNotDestroyedUntilAssignment); + TEST(TestDetachNull); + TEST(TestComparisonWithSharedPtr); + TEST(TestComparisonWithNullptr); + std::cerr << argv[0] << ": Passed: " << passed.size() + << ", failed: " << failed.size() + << "." << std::endl; + return failed.size(); +} diff --git a/src/libsass/test/test_specificity.cpp b/src/libsass/test/test_specificity.cpp deleted file mode 100644 index ba9bbfc46..000000000 --- a/src/libsass/test/test_specificity.cpp +++ /dev/null @@ -1,25 +0,0 @@ -#include "../ast.hpp" -#include "../context.hpp" -#include "../parser.hpp" -#include -#include - -using namespace Sass; - -Context ctx = Context::Data(); - -Selector* selector(std::string src) -{ return Parser::from_c_str(src.c_str(), ctx, "", Position()).parse_selector_list(); } - -void spec(std::string sel) -{ std::cout << sel << "\t::\t" << selector(sel + ";")->specificity() << std::endl; } - -int main() -{ - spec("foo bar hux"); - spec(".foo .bar hux"); - spec("#foo .bar[hux='mux']"); - spec("a b c d e f"); - - return 0; -} diff --git a/src/libsass/test/test_subset_map.cpp b/src/libsass/test/test_subset_map.cpp deleted file mode 100644 index 37945143f..000000000 --- a/src/libsass/test/test_subset_map.cpp +++ /dev/null @@ -1,472 +0,0 @@ -#include -#include -#include -#include "../subset_map.hpp" - -Subset_Map ssm; - -string toString(std::vector v); -string toString(std::vector>> v); -void assertEqual(string std::sExpected, std::string sResult); - -void setup() { - ssm.clear(); - - //@ssm[Set[1, 2]] = "Foo" - std::vector s1; - s1.push_back("1"); - s1.push_back("2"); - ssm.put(s1, "Foo"); - - //@ssm[Set["fizz", "fazz"]] = "Bar" - std::vector s2; - s2.push_back("fizz"); - s2.push_back("fazz"); - ssm.put(s2, "Bar"); - - //@ssm[Set[:foo, :bar]] = "Baz" - std::vector s3; - s3.push_back(":foo"); - s3.push_back(":bar"); - ssm.put(s3, "Baz"); - - //@ssm[Set[:foo, :bar, :baz]] = "Bang" - std::vector s4; - s4.push_back(":foo"); - s4.push_back(":bar"); - s4.push_back(":baz"); - ssm.put(s4, "Bang"); - - //@ssm[Set[:bip, :bop, :blip]] = "Qux" - std::vector s5; - s5.push_back(":bip"); - s5.push_back(":bop"); - s5.push_back(":blip"); - ssm.put(s5, "Qux"); - - //@ssm[Set[:bip, :bop]] = "Thram" - std::vector s6; - s6.push_back(":bip"); - s6.push_back(":bop"); - ssm.put(s6, "Thram"); -} - -void testEqualKeys() { - std::cout << "testEqualKeys" << std::endl; - - //assert_equal [["Foo", Set[1, 2]]], @ssm.get(Set[1, 2]) - std::vector k1; - k1.push_back("1"); - k1.push_back("2"); - assertEqual("[[Foo, Set[1, 2]]]", toString(ssm.get_kv(k1))); - - //assert_equal [["Bar", Set["fizz", "fazz"]]], @ssm.get(Set["fizz", "fazz"]) - std::vector k2; - k2.push_back("fizz"); - k2.push_back("fazz"); - assertEqual("[[Bar, Set[fizz, fazz]]]", toString(ssm.get_kv(k2))); - - std::cout << std::endl; -} - -void testSubsetKeys() { - std::cout << "testSubsetKeys" << std::endl; - - //assert_equal [["Foo", Set[1, 2]]], @ssm.get(Set[1, 2, "fuzz"]) - std::vector k1; - k1.push_back("1"); - k1.push_back("2"); - k1.push_back("fuzz"); - assertEqual("[[Foo, Set[1, 2]]]", toString(ssm.get_kv(k1))); - - //assert_equal [["Bar", Set["fizz", "fazz"]]], @ssm.get(Set["fizz", "fazz", 3]) - std::vector k2; - k2.push_back("fizz"); - k2.push_back("fazz"); - k2.push_back("3"); - assertEqual("[[Bar, Set[fizz, fazz]]]", toString(ssm.get_kv(k2))); - - std::cout << std::endl; -} - -void testSupersetKeys() { - std::cout << "testSupersetKeys" << std::endl; - - //assert_equal [], @ssm.get(Set[1]) - std::vector k1; - k1.push_back("1"); - assertEqual("[]", toString(ssm.get_kv(k1))); - - //assert_equal [], @ssm.get(Set[2]) - std::vector k2; - k2.push_back("2"); - assertEqual("[]", toString(ssm.get_kv(k2))); - - //assert_equal [], @ssm.get(Set["fizz"]) - std::vector k3; - k3.push_back("fizz"); - assertEqual("[]", toString(ssm.get_kv(k3))); - - //assert_equal [], @ssm.get(Set["fazz"]) - std::vector k4; - k4.push_back("fazz"); - assertEqual("[]", toString(ssm.get_kv(k4))); - - std::cout << std::endl; -} - -void testDisjointKeys() { - std::cout << "testDisjointKeys" << std::endl; - - //assert_equal [], @ssm.get(Set[3, 4]) - std::vector k1; - k1.push_back("3"); - k1.push_back("4"); - assertEqual("[]", toString(ssm.get_kv(k1))); - - //assert_equal [], @ssm.get(Set["fuzz", "frizz"]) - std::vector k2; - k2.push_back("fuzz"); - k2.push_back("frizz"); - assertEqual("[]", toString(ssm.get_kv(k2))); - - //assert_equal [], @ssm.get(Set["gran", 15]) - std::vector k3; - k3.push_back("gran"); - k3.push_back("15"); - assertEqual("[]", toString(ssm.get_kv(k3))); - - std::cout << std::endl; -} - -void testSemiDisjointKeys() { - std::cout << "testSemiDisjointKeys" << std::endl; - - //assert_equal [], @ssm.get(Set[2, 3]) - std::vector k1; - k1.push_back("2"); - k1.push_back("3"); - assertEqual("[]", toString(ssm.get_kv(k1))); - - //assert_equal [], @ssm.get(Set["fizz", "fuzz"]) - std::vector k2; - k2.push_back("fizz"); - k2.push_back("fuzz"); - assertEqual("[]", toString(ssm.get_kv(k2))); - - //assert_equal [], @ssm.get(Set[1, "fazz"]) - std::vector k3; - k3.push_back("1"); - k3.push_back("fazz"); - assertEqual("[]", toString(ssm.get_kv(k3))); - - std::cout << std::endl; -} - -void testEmptyKeySet() { - std::cout << "testEmptyKeySet" << std::endl; - - //assert_raises(ArgumentError) {@ssm[Set[]] = "Fail"} - std::vector s1; - try { - ssm.put(s1, "Fail"); - } - catch (const char* &e) { - assertEqual("internal error: subset map keys may not be empty", e); - } -} - -void testEmptyKeyGet() { - std::cout << "testEmptyKeyGet" << std::endl; - - //assert_equal [], @ssm.get(Set[]) - std::vector k1; - assertEqual("[]", toString(ssm.get_kv(k1))); - - std::cout << std::endl; -} -void testMultipleSubsets() { - std::cout << "testMultipleSubsets" << std::endl; - - //assert_equal [["Foo", Set[1, 2]], ["Bar", Set["fizz", "fazz"]]], @ssm.get(Set[1, 2, "fizz", "fazz"]) - std::vector k1; - k1.push_back("1"); - k1.push_back("2"); - k1.push_back("fizz"); - k1.push_back("fazz"); - assertEqual("[[Foo, Set[1, 2]], [Bar, Set[fizz, fazz]]]", toString(ssm.get_kv(k1))); - - //assert_equal [["Foo", Set[1, 2]], ["Bar", Set["fizz", "fazz"]]], @ssm.get(Set[1, 2, 3, "fizz", "fazz", "fuzz"]) - std::vector k2; - k2.push_back("1"); - k2.push_back("2"); - k2.push_back("3"); - k2.push_back("fizz"); - k2.push_back("fazz"); - k2.push_back("fuzz"); - assertEqual("[[Foo, Set[1, 2]], [Bar, Set[fizz, fazz]]]", toString(ssm.get_kv(k2))); - - //assert_equal [["Baz", Set[:foo, :bar]]], @ssm.get(Set[:foo, :bar]) - std::vector k3; - k3.push_back(":foo"); - k3.push_back(":bar"); - assertEqual("[[Baz, Set[:foo, :bar]]]", toString(ssm.get_kv(k3))); - - //assert_equal [["Baz", Set[:foo, :bar]], ["Bang", Set[:foo, :bar, :baz]]], @ssm.get(Set[:foo, :bar, :baz]) - std::vector k4; - k4.push_back(":foo"); - k4.push_back(":bar"); - k4.push_back(":baz"); - assertEqual("[[Baz, Set[:foo, :bar]], [Bang, Set[:foo, :bar, :baz]]]", toString(ssm.get_kv(k4))); - - std::cout << std::endl; -} -void testBracketBracket() { - std::cout << "testBracketBracket" << std::endl; - - //assert_equal ["Foo"], @ssm[Set[1, 2, "fuzz"]] - std::vector k1; - k1.push_back("1"); - k1.push_back("2"); - k1.push_back("fuzz"); - assertEqual("[Foo]", toString(ssm.get_v(k1))); - - //assert_equal ["Baz", "Bang"], @ssm[Set[:foo, :bar, :baz]] - std::vector k2; - k2.push_back(":foo"); - k2.push_back(":bar"); - k2.push_back(":baz"); - assertEqual("[Baz, Bang]", toString(ssm.get_v(k2))); - - std::cout << std::endl; -} - -void testKeyOrder() { - std::cout << "testEqualKeys" << std::endl; - - //assert_equal [["Foo", Set[1, 2]]], @ssm.get(Set[2, 1]) - std::vector k1; - k1.push_back("2"); - k1.push_back("1"); - assertEqual("[[Foo, Set[1, 2]]]", toString(ssm.get_kv(k1))); - - std::cout << std::endl; -} - -void testOrderPreserved() { - std::cout << "testOrderPreserved" << std::endl; - //@ssm[Set[10, 11, 12]] = 1 - std::vector s1; - s1.push_back("10"); - s1.push_back("11"); - s1.push_back("12"); - ssm.put(s1, "1"); - - //@ssm[Set[10, 11]] = 2 - std::vector s2; - s2.push_back("10"); - s2.push_back("11"); - ssm.put(s2, "2"); - - //@ssm[Set[11]] = 3 - std::vector s3; - s3.push_back("11"); - ssm.put(s3, "3"); - - //@ssm[Set[11, 12]] = 4 - std::vector s4; - s4.push_back("11"); - s4.push_back("12"); - ssm.put(s4, "4"); - - //@ssm[Set[9, 10, 11, 12, 13]] = 5 - std::vector s5; - s5.push_back("9"); - s5.push_back("10"); - s5.push_back("11"); - s5.push_back("12"); - s5.push_back("13"); - ssm.put(s5, "5"); - - //@ssm[Set[10, 13]] = 6 - std::vector s6; - s6.push_back("10"); - s6.push_back("13"); - ssm.put(s6, "6"); - - //assert_equal([[1, Set[10, 11, 12]], [2, Set[10, 11]], [3, Set[11]], [4, Set[11, 12]], [5, Set[9, 10, 11, 12, 13]], [6, Set[10, 13]]], @ssm.get(Set[9, 10, 11, 12, 13])) - std::vector k1; - k1.push_back("9"); - k1.push_back("10"); - k1.push_back("11"); - k1.push_back("12"); - k1.push_back("13"); - assertEqual("[[1, Set[10, 11, 12]], [2, Set[10, 11]], [3, Set[11]], [4, Set[11, 12]], [5, Set[9, 10, 11, 12, 13]], [6, Set[10, 13]]]", toString(ssm.get_kv(k1))); - - std::cout << std::endl; -} -void testMultipleEqualValues() { - std::cout << "testMultipleEqualValues" << std::endl; - //@ssm[Set[11, 12]] = 1 - std::vector s1; - s1.push_back("11"); - s1.push_back("12"); - ssm.put(s1, "1"); - - //@ssm[Set[12, 13]] = 2 - std::vector s2; - s2.push_back("12"); - s2.push_back("13"); - ssm.put(s2, "2"); - - //@ssm[Set[13, 14]] = 1 - std::vector s3; - s3.push_back("13"); - s3.push_back("14"); - ssm.put(s3, "1"); - - //@ssm[Set[14, 15]] = 1 - std::vector s4; - s4.push_back("14"); - s4.push_back("15"); - ssm.put(s4, "1"); - - //assert_equal([[1, Set[11, 12]], [2, Set[12, 13]], [1, Set[13, 14]], [1, Set[14, 15]]], @ssm.get(Set[11, 12, 13, 14, 15])) - std::vector k1; - k1.push_back("11"); - k1.push_back("12"); - k1.push_back("13"); - k1.push_back("14"); - k1.push_back("15"); - assertEqual("[[1, Set[11, 12]], [2, Set[12, 13]], [1, Set[13, 14]], [1, Set[14, 15]]]", toString(ssm.get_kv(k1))); - - std::cout << std::endl; -} - -int main() -{ - std::vector s1; - s1.push_back("1"); - s1.push_back("2"); - - std::vector s2; - s2.push_back("2"); - s2.push_back("3"); - - std::vector s3; - s3.push_back("3"); - s3.push_back("4"); - - ssm.put(s1, "value1"); - ssm.put(s2, "value2"); - ssm.put(s3, "value3"); - - std::vector s4; - s4.push_back("1"); - s4.push_back("2"); - s4.push_back("3"); - - std::vector > > fetched(ssm.get_kv(s4)); - - std::cout << "PRINTING RESULTS:" << std::endl; - for (size_t i = 0, S = fetched.size(); i < S; ++i) { - std::cout << fetched[i].first << std::endl; - } - - Subset_Map ssm2; - ssm2.put(s1, "foo"); - ssm2.put(s2, "bar"); - ssm2.put(s4, "hux"); - - std::vector > > fetched2(ssm2.get_kv(s4)); - - std::cout << std::endl << "PRINTING RESULTS:" << std::endl; - for (size_t i = 0, S = fetched2.size(); i < S; ++i) { - std::cout << fetched2[i].first << std::endl; - } - - std::cout << "TRYING ON A SELECTOR-LIKE OBJECT" << std::endl; - - Subset_Map sel_ssm; - std::vector target; - target.push_back("desk"); - target.push_back(".wood"); - - std::vector actual; - actual.push_back("desk"); - actual.push_back(".wood"); - actual.push_back(".mine"); - - sel_ssm.put(target, "has-aquarium"); - std::vector > > fetched3(sel_ssm.get_kv(actual)); - std::cout << "RESULTS:" << std::endl; - for (size_t i = 0, S = fetched3.size(); i < S; ++i) { - std::cout << fetched3[i].first << std::endl; - } - - std::cout << std::endl; - - // BEGIN PORTED RUBY TESTS FROM /test/sass/util/subset_map_test.rb - - setup(); - testEqualKeys(); - testSubsetKeys(); - testSupersetKeys(); - testDisjointKeys(); - testSemiDisjointKeys(); - testEmptyKeySet(); - testEmptyKeyGet(); - testMultipleSubsets(); - testBracketBracket(); - testKeyOrder(); - - setup(); - testOrderPreserved(); - - setup(); - testMultipleEqualValues(); - - return 0; -} - -string toString(std::vector>> v) -{ - std::stringstream buffer; - buffer << "["; - for (size_t i = 0, S = v.size(); i < S; ++i) { - buffer << "[" << v[i].first; - buffer << ", Set["; - for (size_t j = 0, S = v[i].second.size(); j < S; ++j) { - buffer << v[i].second[j]; - if (j < S-1) { - buffer << ", "; - } - } - buffer << "]]"; - if (i < S-1) { - buffer << ", "; - } - } - buffer << "]"; - return buffer.str(); -} - -string toString(std::vector v) -{ - std::stringstream buffer; - buffer << "["; - for (size_t i = 0, S = v.size(); i < S; ++i) { - buffer << v[i]; - if (i < S-1) { - buffer << ", "; - } - } - buffer << "]"; - return buffer.str(); -} - -void assertEqual(string sExpected, string sResult) { - std::cout << "Expected: " << sExpected << std::endl; - std::cout << "Result: " << sResult << std::endl; - assert(sExpected == sResult); -} diff --git a/src/libsass/test/test_superselector.cpp b/src/libsass/test/test_superselector.cpp deleted file mode 100644 index bf21c7c4d..000000000 --- a/src/libsass/test/test_superselector.cpp +++ /dev/null @@ -1,69 +0,0 @@ -#include "../ast.hpp" -#include "../context.hpp" -#include "../parser.hpp" -#include - -using namespace Sass; - -Context ctx = Context(Context::Data()); - -Compound_Selector* compound_selector(std::string src) -{ return Parser::from_c_str(src.c_str(), ctx, "", Position()).parse_compound_selector(); } - -Complex_Selector* complex_selector(std::string src) -{ return Parser::from_c_str(src.c_str(), ctx, "", Position()).parse_complex_selector(false); } - -void check_compound(std::string s1, std::string s2) -{ - std::cout << "Is " - << s1 - << " a superselector of " - << s2 - << "?\t" - << compound_selector(s1 + ";")->is_superselector_of(compound_selector(s2 + ";")) - << std::endl; -} - -void check_complex(std::string s1, std::string s2) -{ - std::cout << "Is " - << s1 - << " a superselector of " - << s2 - << "?\t" - << complex_selector(s1 + ";")->is_superselector_of(complex_selector(s2 + ";")) - << std::endl; -} - -int main() -{ - check_compound(".foo", ".foo.bar"); - check_compound(".foo.bar", ".foo"); - check_compound(".foo.bar", "div.foo"); - check_compound(".foo", "div.foo"); - check_compound("div.foo", ".foo"); - check_compound("div.foo", "div.bar.foo"); - check_compound("p.foo", "div.bar.foo"); - check_compound(".hux", ".mumble"); - - std::cout << std::endl; - - check_complex(".foo ~ .bar", ".foo + .bar"); - check_complex(".foo .bar", ".foo + .bar"); - check_complex(".foo .bar", ".foo > .bar"); - check_complex(".foo .bar > .hux", ".foo.a .bar.b > .hux"); - check_complex(".foo ~ .bar .hux", ".foo.a + .bar.b > .hux"); - check_complex(".foo", ".bar .foo"); - check_complex(".foo", ".foo.a"); - check_complex(".foo.bar", ".foo"); - check_complex(".foo .bar .hux", ".bar .hux"); - check_complex(".foo ~ .bar .hux.x", ".foo.a + .bar.b > .hux.y"); - check_complex(".foo ~ .bar .hux", ".foo.a + .bar.b > .mumble"); - check_complex(".foo + .bar", ".foo ~ .bar"); - check_complex("a c e", "a b c d e"); - check_complex("c a e", "a b c d e"); - - return 0; -} - - diff --git a/src/libsass/test/test_unification.cpp b/src/libsass/test/test_unification.cpp deleted file mode 100644 index 5c663ee90..000000000 --- a/src/libsass/test/test_unification.cpp +++ /dev/null @@ -1,31 +0,0 @@ -#include "../ast.hpp" -#include "../context.hpp" -#include "../parser.hpp" -#include - -using namespace Sass; - -Context ctx = Context(Context::Data()); - -Compound_Selector* selector(std::string src) -{ return Parser::from_c_str(src.c_str(), ctx, "", Position()).parse_compound_selector(); } - -void unify(std::string lhs, std::string rhs) -{ - Compound_Selector* unified = selector(lhs + ";")->unify_with(selector(rhs + ";"), ctx); - std::cout << lhs << " UNIFIED WITH " << rhs << " =\t" << (unified ? unified->to_string() : "NOTHING") << std::endl; -} - -int main() -{ - unify(".foo", ".foo.bar"); - unify("div:nth-of-type(odd)", "div:first-child"); - unify("div", "span:whatever"); - unify("div", "span"); - unify("foo:bar::after", "foo:bar::first-letter"); - unify(".foo#bar.hux", ".hux.foo#bar"); - unify(".foo#bar.hux", ".hux.foo#baz"); - unify("*:blah:fudge", "p:fudge:blah"); - - return 0; -} diff --git a/src/libsass/test/test_util_string.cpp b/src/libsass/test/test_util_string.cpp new file mode 100644 index 000000000..b0df8af2c --- /dev/null +++ b/src/libsass/test/test_util_string.cpp @@ -0,0 +1,217 @@ +#include "../src/util_string.hpp" + +#include +#include +#include +#include + +namespace { + +std::string escape_string(const std::string& str) { + std::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() { + std::string input = "a"; + std::string normalized = Sass::Util::normalize_newlines(input); + ASSERT_STR_EQ(input, normalized); + return true; +} + +bool TestNormalizeNewlinesLF() { + std::string input = "a\nb"; + std::string normalized = Sass::Util::normalize_newlines(input); + ASSERT_STR_EQ(input, normalized); + return true; +} + +bool TestNormalizeNewlinesCR() { + std::string normalized = Sass::Util::normalize_newlines("a\rb"); + ASSERT_STR_EQ("a\nb", normalized); + return true; +} + +bool TestNormalizeNewlinesCRLF() { + std::string normalized = Sass::Util::normalize_newlines("a\r\nb\r\n"); + ASSERT_STR_EQ("a\nb\n", normalized); + return true; +} + +bool TestNormalizeNewlinesFF() { + std::string normalized = Sass::Util::normalize_newlines("a\fb\f"); + ASSERT_STR_EQ("a\nb\n", normalized); + return true; +} + +bool TestNormalizeNewlinesMixed() { + std::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() { + std::string normalized = Sass::Util::normalize_underscores("a_b_c"); + ASSERT_STR_EQ("a-b-c", normalized); + return true; +} + +bool TestNormalizeDecimalsLeadingZero() { + std::string normalized = Sass::Util::normalize_decimals("0.5"); + ASSERT_STR_EQ("0.5", normalized); + return true; +} + +bool TestNormalizeDecimalsNoLeadingZero() { + std::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")); + ASSERT_FALSE(Sass::Util::equalsLiteral("moz", ":moz")); + ASSERT_FALSE(Sass::Util::equalsLiteral(":moz", "moz")); + ASSERT_TRUE(Sass::Util::equalsLiteral("moz-foo", "MOZ-foo")); + ASSERT_FALSE(Sass::Util::equalsLiteral("moz-foo", "moz_foo")); + ASSERT_TRUE(Sass::Util::equalsLiteral("moz-foo", "MOZ-FOOS")); + ASSERT_FALSE(Sass::Util::equalsLiteral("moz-foos", "moz-foo")); + ASSERT_FALSE(Sass::Util::equalsLiteral("-moz-foo", "moz-foo")); + return true; + +} + +bool TestUnvendor() { + // Generated by using dart sass + ASSERT_STR_EQ("moz", Sass::Util::unvendor("moz")); + ASSERT_STR_EQ(":moz", Sass::Util::unvendor(":moz")); + ASSERT_STR_EQ("-moz", Sass::Util::unvendor("-moz")); + ASSERT_STR_EQ("--moz", Sass::Util::unvendor("--moz")); + ASSERT_STR_EQ("moz-bar", Sass::Util::unvendor("moz-bar")); + ASSERT_STR_EQ("bar", Sass::Util::unvendor("-moz-bar")); + ASSERT_STR_EQ("bar-", Sass::Util::unvendor("-moz-bar-")); + ASSERT_STR_EQ("--moz-bar", Sass::Util::unvendor("--moz-bar")); + ASSERT_STR_EQ("-bar", Sass::Util::unvendor("-moz--bar")); + ASSERT_STR_EQ("any", Sass::Util::unvendor("-s-any")); + ASSERT_STR_EQ("any-more", Sass::Util::unvendor("-s-any-more")); + ASSERT_STR_EQ("any--more", Sass::Util::unvendor("-s-any--more")); + ASSERT_STR_EQ("--s-any--more", Sass::Util::unvendor("--s-any--more")); + ASSERT_STR_EQ("s-any--more", Sass::Util::unvendor("s-any--more")); + ASSERT_STR_EQ("_s_any_more", Sass::Util::unvendor("_s_any_more")); + ASSERT_STR_EQ("more", Sass::Util::unvendor("-s_any-more")); + ASSERT_STR_EQ("any_more", Sass::Util::unvendor("-s-any_more")); + ASSERT_STR_EQ("_s_any_more", Sass::Util::unvendor("_s_any_more")); + return true; +} + +bool Test_ascii_str_to_lower() { + std::string str = "A B"; + Sass::Util::ascii_str_tolower(&str); + ASSERT_STR_EQ("a b", str); + return true; +} + +bool Test_ascii_str_to_upper() { + std::string str = "a b"; + Sass::Util::ascii_str_toupper(&str); + ASSERT_STR_EQ("A B", str); + return true; +} + +bool Test_ascii_isalpha() { + ASSERT_TRUE(Sass::Util::ascii_isalpha('a')); + ASSERT_FALSE(Sass::Util::ascii_isalpha('3')); + return true; +} + +bool Test_ascii_isxdigit() { + ASSERT_TRUE(Sass::Util::ascii_isxdigit('a')); + ASSERT_TRUE(Sass::Util::ascii_isxdigit('F')); + ASSERT_TRUE(Sass::Util::ascii_isxdigit('3')); + ASSERT_FALSE(Sass::Util::ascii_isxdigit('G')); + return true; +} + +bool Test_ascii_isspace() { + ASSERT_TRUE(Sass::Util::ascii_isspace(' ')); + ASSERT_TRUE(Sass::Util::ascii_isspace('\t')); + ASSERT_TRUE(Sass::Util::ascii_isspace('\v')); + ASSERT_TRUE(Sass::Util::ascii_isspace('\f')); + ASSERT_TRUE(Sass::Util::ascii_isspace('\r')); + ASSERT_TRUE(Sass::Util::ascii_isspace('\n')); + ASSERT_FALSE(Sass::Util::ascii_isspace('G')); + return true; +} + +} // namespace + +#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; + TEST(TestNormalizeNewlinesNoNewline); + TEST(TestNormalizeNewlinesLF); + TEST(TestNormalizeNewlinesCR); + TEST(TestNormalizeNewlinesCRLF); + TEST(TestNormalizeNewlinesFF); + TEST(TestNormalizeNewlinesMixed); + TEST(TestNormalizeUnderscores); + TEST(TestNormalizeDecimalsLeadingZero); + TEST(TestNormalizeDecimalsNoLeadingZero); + TEST(testEqualsLiteral); + TEST(TestUnvendor); + 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(); +} diff --git a/src/libsass/win/libsass.targets b/src/libsass/win/libsass.targets index c1c7d45f3..ba83de4c1 100644 --- a/src/libsass/win/libsass.targets +++ b/src/libsass/win/libsass.targets @@ -11,9 +11,11 @@ + + + - @@ -30,16 +32,23 @@ - + + - + + + + + + + + - @@ -51,11 +60,11 @@ - - - + + + @@ -64,10 +73,18 @@ + + + + + + + + @@ -83,17 +100,26 @@ + - + + - + + + + + + + + - + @@ -103,16 +129,17 @@ - - - + + + + diff --git a/src/libsass/win/libsass.vcxproj b/src/libsass/win/libsass.vcxproj index 8cfd61f2a..242e8ed69 100644 --- a/src/libsass/win/libsass.vcxproj +++ b/src/libsass/win/libsass.vcxproj @@ -66,6 +66,12 @@ v140 + + v141 + + + v142 + true diff --git a/src/libsass/win/libsass.vcxproj.filters b/src/libsass/win/libsass.vcxproj.filters index 980f00f3f..2f8666869 100644 --- a/src/libsass/win/libsass.vcxproj.filters +++ b/src/libsass/win/libsass.vcxproj.filters @@ -43,18 +43,33 @@ Include Headers + + Sources + + + Sources + + + Sources + Headers - + Headers - + + Headers + + Headers - + + Headers + + Headers @@ -102,13 +117,31 @@ Headers - + + Headers + + Headers - + + Headers + + Headers - + + Headers + + + Headers + + + Headers + + + Headers + + Headers @@ -129,9 +162,6 @@ Headers - - Headers - Headers @@ -162,19 +192,16 @@ Headers - - Headers - Headers Headers - + Headers - + Headers @@ -201,6 +228,9 @@ Headers + + Headers + Headers @@ -215,6 +245,21 @@ Sources + + Sources + + + Sources + + + Sources + + + Sources + + + Sources + Sources @@ -263,13 +308,31 @@ Sources - + Sources - + + Sources + + + Sources + + + Sources + + + Sources + + Sources - + + Sources + + + Sources + + Sources @@ -284,9 +347,6 @@ Sources - - Sources - Sources @@ -320,9 +380,6 @@ Sources - - Sources - Sources @@ -332,10 +389,10 @@ Sources - + Sources - + Sources @@ -350,8 +407,32 @@ Sources + + Sources + Sources + + Sources + + + Sources + + + Sources + + + Sources + + + Sources + + + Sources + + + Sources + diff --git a/test/fixtures/depth-first/_common.scss b/test/fixtures/depth-first/_common.scss index 7b30f5e2b..e13d024d0 100644 --- a/test/fixtures/depth-first/_common.scss +++ b/test/fixtures/depth-first/_common.scss @@ -2,5 +2,5 @@ @import "struct"; .myvars { - content: quote($import_counter); + content: quote(#{$import_counter}); }