diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 86975970ae..64def14a15 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -13,37 +13,41 @@ on: jobs: linux-and-mac: + name: Linux and MacOS build # if: ${{ false }} runs-on: ${{ matrix.config.os }} - name: ${{ matrix.config.os }} BUILD=${{ matrix.config.build }} CC=${{ matrix.config.cc }} CXX=${{ matrix.config.cxx }} AUTOTOOLS=${{ matrix.config.autotools }} strategy: - fail-fast: false + # fail-fast: false matrix: config: #- {os: ubuntu-16.04, build: 'static', cc: 'gcc-4.4', cxx: 'g++-4.4', autotools: 'no', cppstd: 'gnu++0x'} #- {os: ubuntu-16.04, build: 'static', cc: 'gcc-4.6', cxx: 'g++-4.6', autotools: 'no', cppstd: 'gnu++0x'} - - {os: ubuntu-16.04, build: 'static', cc: 'gcc-4.7', cxx: 'g++-4.7', autotools: 'no', cppstd: 'gnu++11'} - - {os: ubuntu-16.04, build: 'static', cc: 'gcc-4.8', cxx: 'g++-4.8', autotools: 'no', cppstd: 'c++11'} + #- {os: ubuntu-16.04, build: 'static', cc: 'gcc-4.7', cxx: 'g++-4.7', autotools: 'no', cppstd: 'gnu++11'} + #- {os: ubuntu-16.04, build: 'static', cc: 'gcc-4.8', cxx: 'g++-4.8', autotools: 'no', cppstd: 'c++11'} - {os: ubuntu-16.04, build: 'static', cc: 'gcc-5', cxx: 'g++-5', autotools: 'no', cppstd: 'c++11'} - {os: ubuntu-16.04, build: 'static', cc: 'gcc-6', cxx: 'g++-6', autotools: 'no', cppstd: 'c++11'} - {os: ubuntu-latest, build: 'static', cc: 'gcc-7', cxx: 'g++-7', autotools: 'no', cppstd: 'c++11'} - - {os: ubuntu-latest, build: 'shared', cc: 'gcc', cxx: 'g++', autotools: 'yes', cppstd: 'c++11'} - - {os: ubuntu-latest, build: 'static', cc: 'gcc', cxx: 'g++', autotools: 'yes', cppstd: 'c++11'} + - {os: ubuntu-latest, build: 'shared', cc: 'gcc', cxx: 'g++', autotools: 'yes', cppstd: 'c++11', cflags: '-g -fsanitize=address'} + - {os: ubuntu-latest, build: 'static', cc: 'gcc', cxx: 'g++', autotools: 'yes', cppstd: 'c++11', cflags: '-g -fsanitize=address'} - {os: ubuntu-latest, build: 'shared', cc: 'gcc', cxx: 'g++', autotools: 'no', cppstd: 'c++11'} - {os: ubuntu-latest, build: 'static', cc: 'gcc', cxx: 'g++', autotools: 'no', cppstd: 'c++11'} - - {os: ubuntu-latest, build: 'shared', cc: 'clang', cxx: 'clang++', autotools: 'yes', cppstd: 'c++11'} - - {os: ubuntu-latest, build: 'static', cc: 'clang', cxx: 'clang++', autotools: 'yes', cppstd: 'c++11'} - - {os: ubuntu-latest, build: 'shared', cc: 'clang', cxx: 'clang++', autotools: 'no', cppstd: 'c++11'} - - {os: ubuntu-latest, build: 'static', cc: 'clang', cxx: 'clang++', autotools: 'no', cppstd: 'c++11'} - - {os: macOS-latest, build: 'shared', cc: 'clang', cxx: 'clang++', autotools: 'yes', cppstd: 'c++11'} - - {os: macOS-latest, build: 'static', cc: 'clang', cxx: 'clang++', autotools: 'yes', cppstd: 'c++11'} - - {os: macOS-latest, build: 'shared', cc: 'clang', cxx: 'clang++', autotools: 'no', cppstd: 'c++11'} - - {os: macOS-latest, build: 'static', cc: 'clang', cxx: 'clang++', autotools: 'no', cppstd: 'c++11'} + - {os: ubuntu-latest, build: 'shared', cc: 'clang', cxx: 'clang++', autotools: 'yes', cppstd: 'c++11', cflags: '-g -fsanitize=address'} + - {os: ubuntu-latest, build: 'static', cc: 'clang', cxx: 'clang++', autotools: 'yes', cppstd: 'c++11', cflags: '-g -fsanitize=address'} + - {os: ubuntu-latest, build: 'shared', cc: 'clang', cxx: 'clang++', autotools: 'no', cppstd: 'c++11', cflags: '-g -fsanitize=address'} + - {os: ubuntu-latest, build: 'static', cc: 'clang', cxx: 'clang++', autotools: 'no', cppstd: 'c++11', cflags: '-g -fsanitize=address'} + - {os: macOS-latest, build: 'shared', cc: 'clang', cxx: 'clang++', autotools: 'yes', cppstd: 'c++11', cflags: '-g -fsanitize=address'} + - {os: macOS-latest, build: 'static', cc: 'clang', cxx: 'clang++', autotools: 'yes', cppstd: 'c++11', cflags: '-g -fsanitize=address'} + - {os: macOS-latest, build: 'shared', cc: 'clang', cxx: 'clang++', autotools: 'no', cppstd: 'c++11', cflags: '-g -fsanitize=address'} + - {os: macOS-latest, build: 'static', cc: 'clang', cxx: 'clang++', autotools: 'no', cppstd: 'c++11', cflags: '-g -fsanitize=address'} env: - ASAN_OPTIONS: detect_odr_violation=0 + ASAN_OPTIONS: ${{ matrix.config.asan }} AUTOTOOLS: ${{ matrix.config.autotools }} + EXTRA_CFLAGS: ${{ matrix.config.cflags }} + EXTRA_CXXFLAGS: ${{ matrix.config.cflags }} + EXTRA_LDFLAGS: ${{ matrix.config.cflags }} + SASS_LIBSASS_PATH: libsass COVERAGE: no BUILD: ${{ matrix.config.build }} CXX: ${{ matrix.config.cxx }} @@ -125,20 +129,31 @@ jobs: env: MAKE_OPTS: LIBSASS_CPPSTD=${{ matrix.config.cppstd }} run: ./script/ci-build-libsass + - name: Call LibSass make install + run: sudo EXTRA_CFLAGS="$EXTRA_CFLAGS" EXTRA_CXXFLAGS="$EXTRA_CXXFLAGS" EXTRA_LDFLAGS="$EXTRA_LDFLAGS" make install + - name: Call SassC make install + run: sudo EXTRA_CFLAGS="$EXTRA_CFLAGS" EXTRA_CXXFLAGS="$EXTRA_CXXFLAGS" EXTRA_LDFLAGS="$EXTRA_LDFLAGS" make -C sassc install + - name: Check sassc help call + run: sassc --term-colors --help windows-msvc: - runs-on: windows-latest + runs-on: ${{ matrix.config.os }} name: Windows MSVC build + # if: ${{ false }} strategy: - fail-fast: false + # fail-fast: false matrix: config: - - {build: Release, platform: Win64} - - {build: Debug, platform: Win64} - - {build: Release, platform: Win32} - - {build: Debug, platform: Win32} + - {os: 'windows-latest', build: Release, platform: Win64, version: 'latest'} + - {os: 'windows-latest', build: Debug, platform: Win64, version: 'latest'} + - {os: 'windows-latest', build: Release, platform: Win32, version: 'latest'} + - {os: 'windows-latest', build: Debug, platform: Win32, version: 'latest'} + - {os: 'windows-2016', build: Release, platform: Win64, version: '[15.0,16.0]'} + - {os: 'windows-2016', build: Debug, platform: Win64, version: '[15.0,16.0]'} + - {os: 'windows-2016', build: Release, platform: Win32, version: '[15.0,16.0]'} + - {os: 'windows-2016', build: Debug, platform: Win32, version: '[15.0,16.0]'} steps: - name: Change git config to preserve line-endings @@ -156,13 +171,18 @@ jobs: run: gem install minitest - name: Add msbuild to PATH uses: microsoft/setup-msbuild@v1.0.2 + with: + vs-version: ${{ matrix.config.version }} - name: Clone and checkout sassc repository - run: git clone https://github.com/sass/sassc.git + run: git clone https://github.com/mgreter/sassc.git --branch refactor/libsass-4-alpha - name: Clone and checkout sass-spec repository - run: git clone https://github.com/sass/sass-spec.git + run: git clone https://github.com/mgreter/sass-spec.git --branch refactor/libsass-4-alpha - name: Compile libsass ${{ matrix.config.build }} build for ${{ matrix.config.platform }} - run: msbuild /m:4 /p:"Configuration=${{ matrix.config.build }};Platform=${{ matrix.config.platform }}" sassc\win\sassc.sln + run: msbuild /m:4 /p:Configuration=${{ matrix.config.build }} /p:Platform=${{ matrix.config.platform }} sassc\win\sassc.sln + - name: Check sassc help call + run: sassc\bin\sassc.exe --term-colors --help - name: Execute spec test runner + if: matrix.config.build != 'Debug' run: ruby sass-spec/sass-spec.rb --probe-todo --impl libsass -c sassc/bin/sassc.exe -s sass-spec/spec windows-mingw: @@ -170,7 +190,7 @@ jobs: name: Windows MinGW build strategy: - fail-fast: false + # fail-fast: false matrix: config: - {build: shared, platform: x64} @@ -178,6 +198,13 @@ jobs: - {build: shared, platform: x86} - {build: static, platform: x86} + env: + ASAN_OPTIONS: ${{ matrix.config.asan }} + EXTRA_CFLAGS: ${{ matrix.config.cflags }} + EXTRA_CXXFLAGS: ${{ matrix.config.cflags }} + #SASS_LIBSASS_PATH: libsass + #COVERAGE: no + steps: - name: Change git config to preserve line-endings run: | @@ -197,19 +224,20 @@ jobs: - name: Install ruby minitest module run: gem install minitest - name: Clone and checkout sassc repository - run: git clone https://github.com/sass/sassc.git + run: git clone https://github.com/mgreter/sassc.git --branch refactor/libsass-4-alpha - name: Clone and checkout sass-spec repository - run: git clone https://github.com/sass/sass-spec.git - - name: Add libsass library path to be found - if: matrix.config.build == 'shared' - run: echo "/d/a/libsass/libsass/lib" >> $GITHUB_PATH + run: git clone https://github.com/mgreter/sass-spec.git --branch refactor/libsass-4-alpha - name: Compile libsass ${{ matrix.config.build }} build for ${{ matrix.config.platform }} - run: make ${{ matrix.config.build }} BUILD=${{ matrix.config.build }} + run: make -j5 ${{ matrix.config.build }} BUILD=${{ matrix.config.build }} CC=gcc + - name: Listening results + run: dir lib - name: Copy library over to pass call test if: matrix.config.build == 'shared' - run: copy /a/libsass/libsass/lib/libsass.dll sassc/bin/ + run: copy lib\libsass.dll sassc\bin - name: Compile sassc ${{ matrix.config.build }} build for ${{ matrix.config.platform }} - run: make sassc BUILD=${{ matrix.config.build }} + run: make -j5 sassc BUILD=${{ matrix.config.build }} CC=gcc + - name: Check sassc help call + run: sassc\bin\sassc.exe --term-colors --help - name: Execute spec test runner run: ruby sass-spec/sass-spec.rb --probe-todo --impl libsass -c sassc/bin/sassc.exe -s sass-spec/spec diff --git a/.gitignore b/.gitignore index ef3ca02205..5eb2d2b939 100644 --- a/.gitignore +++ b/.gitignore @@ -53,6 +53,10 @@ libsass/* *.lo *.so *.dll +*.h.gch +*.h.pch +*.hpp.gch +*.hpp.pch *.a *.suo *.sdf diff --git a/GNUmakefile.am b/GNUmakefile.am index 06a1d0c1e6..5a58c3eb4c 100644 --- a/GNUmakefile.am +++ b/GNUmakefile.am @@ -29,7 +29,7 @@ if ENABLE_TESTS SASS_SASSC_PATH ?= $(top_srcdir)/sassc SASS_SPEC_PATH ?= $(top_srcdir)/sass-spec -LIBSASS_SPEC_PATH ?= $(top_srcdir)/libsass-spec +SASS_SPEC_ROOT ?= $(top_srcdir)/sass-spec noinst_PROGRAMS = tester tester_LDADD = src/libsass.la @@ -50,41 +50,25 @@ AM_RB_LOG_FLAGS = $(RUBY) SASS_TEST_FLAGS = --impl libsass SASS_TEST_FLAGS += -r $(SASS_SPEC_PATH)/spec SASS_TEST_FLAGS += -c $(top_srcdir)/tester$(EXEEXT) -LIBSASS_TEST_FLAGS = --impl libsass -LIBSASS_TEST_FLAGS += -r $(LIBSASS_SPEC_PATH)/spec -LIBSASS_TEST_FLAGS += -c $(top_srcdir)/tester$(EXEEXT) -COMPRESSED_TEST_FLAGS = --impl libsass -COMPRESSED_TEST_FLAGS += -r $(LIBSASS_SPEC_PATH)/styles/compressed -COMPRESSED_TEST_FLAGS += -c $(top_srcdir)/tester$(EXEEXT) -COMPRESSED_TEST_FLAGS += --cmd-args="-t compressed" +SASS_TEST_FLAGS += --cmd-args "-I $(SASS_SPEC_ROOT)/spec" AM_TESTS_ENVIRONMENT = TEST_FLAGS='$(SASS_TEST_FLAGS)' SASS_TESTER = $(RUBY) $(SASS_SPEC_PATH)/sass-spec.rb test: $(SASS_TESTER) $(SASS_TEST_FLAGS) - $(SASS_TESTER) $(LIBSASS_TEST_FLAGS) - $(SASS_TESTER) $(COMPRESSED_TEST_FLAGS) test_build: $(SASS_TESTER) $(SASS_TEST_FLAGS) - $(SASS_TESTER) $(LIBSASS_TEST_FLAGS) - $(SASS_TESTER) $(COMPRESSED_TEST_FLAGS) test_full: $(SASS_TESTER) --run-todo $(SASS_TEST_FLAGS) - $(SASS_TESTER) --run-todo $(LIBSASS_TEST_FLAGS) - $(SASS_TESTER) --run-todo $(COMPRESSED_TEST_FLAGS) test_probe: $(SASS_TESTER) --probe-todo $(SASS_TEST_FLAGS) - $(SASS_TESTER) --probe-todo $(LIBSASS_TEST_FLAGS) - $(SASS_TESTER) --probe-todo $(COMPRESSED_TEST_FLAGS) test_interactive: $(SASS_TESTER) --interactive $(SASS_TEST_FLAGS) - $(SASS_TESTER) --interactive $(LIBSASS_TEST_FLAGS) - $(SASS_TESTER) --interactive $(COMPRESSED_TEST_FLAGS) .PHONY: test test_build test_full test_probe diff --git a/Makefile b/Makefile index 4b6637f1aa..395828283f 100644 --- a/Makefile +++ b/Makefile @@ -16,14 +16,24 @@ CFLAGS ?= -Wall CXXFLAGS ?= -Wall LDFLAGS ?= -Wall ifndef COVERAGE - CFLAGS += -O2 - CXXFLAGS += -O2 - LDFLAGS += -O2 + CFLAGS += -O3 -pipe -DNDEBUG -fomit-frame-pointer + CXXFLAGS += -O3 -pipe -DNDEBUG -fomit-frame-pointer + LDFLAGS += -O3 -pipe -DNDEBUG -fomit-frame-pointer else CFLAGS += -O1 -fno-omit-frame-pointer CXXFLAGS += -O1 -fno-omit-frame-pointer LDFLAGS += -O1 -fno-omit-frame-pointer endif +ifeq "$(LIBSASS_GPO)" "generate" + CFLAGS += -fprofile-generate + CXXFLAGS += -fprofile-generate + LDFLAGS += -fprofile-generate -Wl,-fprofile-instr-generate +endif +ifeq "$(LIBSASS_GPO)" "use" + CFLAGS += -fprofile-use + CXXFLAGS += -fprofile-use + LDFLAGS += -fprofile-use -Wl,-fprofile-instr-use +endif CAT ?= $(if $(filter $(OS),Windows_NT),type,cat) ifneq (,$(findstring /cygdrive/,$(PATH))) @@ -71,6 +81,7 @@ else STATIC_ALL ?= 0 STATIC_LIBGCC ?= 0 STATIC_LIBSTDCPP ?= 0 + MKDIR += -p endif ifndef SASS_LIBSASS_PATH @@ -85,6 +96,9 @@ else CXXFLAGS += -I include endif +CFLAGS += -I $(SASS_LIBSASS_PATH)/src +CXXFLAGS += -I $(SASS_LIBSASS_PATH)/src + CFLAGS += $(EXTRA_CFLAGS) CXXFLAGS += $(EXTRA_CXXFLAGS) LDFLAGS += $(EXTRA_LDFLAGS) @@ -145,7 +159,7 @@ SASS_SASSC_PATH ?= sassc SASS_SPEC_PATH ?= sass-spec SASS_SPEC_SPEC_DIR ?= spec LIBSASS_SPEC_PATH ?= libsass-spec -LIBSASS_SPEC_SPEC_DIR ?= spec +LIBSASS_SPEC_SPEC_DIR ?= suites SASSC_BIN = $(SASS_SASSC_PATH)/bin/sassc RUBY_BIN = ruby @@ -178,6 +192,7 @@ endif include Makefile.conf OBJECTS = $(addprefix src/,$(SOURCES:.cpp=.o)) COBJECTS = $(addprefix src/,$(CSOURCES:.c=.o)) +HEADOBJS = $(addprefix src/,$(HPPFILES:.hpp=.hpp.gch)) RCOBJECTS = $(RESOURCES:.rc=.o) DEBUG_LVL ?= NONE @@ -186,6 +201,7 @@ CLEANUPS ?= CLEANUPS += $(RCOBJECTS) CLEANUPS += $(COBJECTS) CLEANUPS += $(OBJECTS) +CLEANUPS += $(HEADOBJS) CLEANUPS += $(LIBSASS_LIB) all: $(BUILD) @@ -216,18 +232,21 @@ lib/libsass.dylib: $(COBJECTS) $(OBJECTS) | lib -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 $@ $< + $(CXX) $(LDFLAGS) $(COBJECTS) $(OBJECTS) $(RCOBJECTS) $(LDLIBS) -shared -o $@ \ + -Wl,--subsystem,windows,--out-implib,lib/libsass.dll.a,--output-def,lib/libsass.dll.def -%.o: %.rc +$(RCOBJECTS): %.o: %.rc $(WINDRES) -i $< -o $@ -%.o: %.cpp +$(OBJECTS): %.o: %.cpp $(CXX) $(CXXFLAGS) -c -o $@ $< +$(COBJECTS): %.o: %.c + $(CC) $(CFLAGS) -c -o $@ $< + +$(HEADOBJS): %.hpp.gch: %.hpp + $(CXX) $(CXXFLAGS) -x c++-header -c -o $@ $< + %: %.o static $(CXX) $(CXXFLAGS) -o $@ $+ $(LDFLAGS) $(LDLIBS) @@ -253,12 +272,18 @@ $(DESTDIR)$(PREFIX)/include/%.h: include/%.h \ $(INSTALL) -v -m0644 "$<" "$@" install-headers: $(DESTDIR)$(PREFIX)/include/sass.h \ - $(DESTDIR)$(PREFIX)/include/sass2scss.h \ $(DESTDIR)$(PREFIX)/include/sass/base.h \ - $(DESTDIR)$(PREFIX)/include/sass/version.h \ + $(DESTDIR)$(PREFIX)/include/sass/compiler.h \ + $(DESTDIR)$(PREFIX)/include/sass/enums.h \ + $(DESTDIR)$(PREFIX)/include/sass/error.h \ + $(DESTDIR)$(PREFIX)/include/sass/function.h \ + $(DESTDIR)$(PREFIX)/include/sass/fwdecl.h \ + $(DESTDIR)$(PREFIX)/include/sass/import.h \ + $(DESTDIR)$(PREFIX)/include/sass/importer.h \ + $(DESTDIR)$(PREFIX)/include/sass/traces.h \ $(DESTDIR)$(PREFIX)/include/sass/values.h \ - $(DESTDIR)$(PREFIX)/include/sass/context.h \ - $(DESTDIR)$(PREFIX)/include/sass/functions.h + $(DESTDIR)$(PREFIX)/include/sass/variable.h \ + $(DESTDIR)$(PREFIX)/include/sass/version.h $(DESTDIR)$(PREFIX)/lib/%.a: lib/%.a \ | $(DESTDIR)$(PREFIX)/lib @@ -303,13 +328,13 @@ test_build: $(SASSC_BIN) $(SASS_SPEC_PATH) $(LIBSASS_SPEC_PATH) --cmd-args "-I $(SASS_SPEC_PATH)/$(SASS_SPEC_SPEC_DIR)" \ $(LOG_FLAGS) $(SASS_SPEC_PATH)/$(SASS_SPEC_SPEC_DIR) $(RUBY_BIN) $(SASS_SPEC_PATH)/sass-spec.rb -c $(SASSC_BIN) --impl libsass \ - --cmd-args "-I $(LIBSASS_SPEC_PATH)/$(LIBSASS_SPEC_SPEC_DIR)" \ + --cmd-args "-I . -I $(LIBSASS_SPEC_PATH)/$(LIBSASS_SPEC_SPEC_DIR)" \ $(LOG_FLAGS) $(LIBSASS_SPEC_PATH)/$(LIBSASS_SPEC_SPEC_DIR) $(RUBY_BIN) $(SASS_SPEC_PATH)/sass-spec.rb -c $(SASSC_BIN) --impl libsass \ - --cmd-args "-I $(LIBSASS_SPEC_PATH)/styles/compressed -t compressed" \ + --cmd-args "-I . -I $(LIBSASS_SPEC_PATH)/styles/compressed -t compressed" \ $(LOG_FLAGS) $(LIBSASS_SPEC_PATH)/styles/compressed $(RUBY_BIN) $(SASS_SPEC_PATH)/sass-spec.rb -c $(SASSC_BIN) --impl libsass \ - --cmd-args "-I $(LIBSASS_SPEC_PATH)/styles/nested -t nested" \ + --cmd-args "-I . -I $(LIBSASS_SPEC_PATH)/styles/nested -t nested" \ $(LOG_FLAGS) $(LIBSASS_SPEC_PATH)/styles/nested test_full: $(SASSC_BIN) $(SASS_SPEC_PATH) $(LIBSASS_SPEC_PATH) @@ -317,13 +342,13 @@ test_full: $(SASSC_BIN) $(SASS_SPEC_PATH) $(LIBSASS_SPEC_PATH) --cmd-args "-I $(SASS_SPEC_PATH)/$(SASS_SPEC_SPEC_DIR)" \ --run-todo $(LOG_FLAGS) $(SASS_SPEC_PATH)/$(SASS_SPEC_SPEC_DIR) $(RUBY_BIN) $(SASS_SPEC_PATH)/sass-spec.rb -c $(SASSC_BIN) --impl libsass \ - --cmd-args "-I $(LIBSASS_SPEC_PATH)/$(LIBSASS_SPEC_SPEC_DIR)" \ + --cmd-args "-I . -I $(LIBSASS_SPEC_PATH)/$(LIBSASS_SPEC_SPEC_DIR)" \ --run-todo $(LOG_FLAGS) $(LIBSASS_SPEC_PATH)/$(LIBSASS_SPEC_SPEC_DIR) $(RUBY_BIN) $(SASS_SPEC_PATH)/sass-spec.rb -c $(SASSC_BIN) --impl libsass \ - --cmd-args "-I $(LIBSASS_SPEC_PATH)/styles/compressed -t compressed" \ + --cmd-args "-I . -I $(LIBSASS_SPEC_PATH)/styles/compressed -t compressed" \ --run-todo $(LOG_FLAGS) $(LIBSASS_SPEC_PATH)/styles/compressed $(RUBY_BIN) $(SASS_SPEC_PATH)/sass-spec.rb -c $(SASSC_BIN) --impl libsass \ - --cmd-args "-I $(LIBSASS_SPEC_PATH)/styles/nested -t nested" \ + --cmd-args "-I . -I $(LIBSASS_SPEC_PATH)/styles/nested -t nested" \ --run-todo $(LOG_FLAGS) $(LIBSASS_SPEC_PATH)/styles/nested test_probe: $(SASSC_BIN) $(SASS_SPEC_PATH) $(LIBSASS_SPEC_PATH) @@ -331,13 +356,13 @@ test_probe: $(SASSC_BIN) $(SASS_SPEC_PATH) $(LIBSASS_SPEC_PATH) --cmd-args "-I $(SASS_SPEC_PATH)/$(SASS_SPEC_SPEC_DIR)" \ --probe-todo $(LOG_FLAGS) $(SASS_SPEC_PATH)/$(SASS_SPEC_SPEC_DIR) $(RUBY_BIN) $(SASS_SPEC_PATH)/sass-spec.rb -c $(SASSC_BIN) --impl libsass \ - --cmd-args "-I $(LIBSASS_SPEC_PATH)/$(LIBSASS_SPEC_SPEC_DIR)" \ + --cmd-args "-I . -I $(LIBSASS_SPEC_PATH)/$(LIBSASS_SPEC_SPEC_DIR)" \ --probe-todo $(LOG_FLAGS) $(LIBSASS_SPEC_PATH)/$(LIBSASS_SPEC_SPEC_DIR) $(RUBY_BIN) $(SASS_SPEC_PATH)/sass-spec.rb -c $(SASSC_BIN) --impl libsass \ - --cmd-args "-I $(LIBSASS_SPEC_PATH)/styles/compressed -t compressed" \ + --cmd-args "-I . -I $(LIBSASS_SPEC_PATH)/styles/compressed -t compressed" \ --probe-todo $(LOG_FLAGS) $(LIBSASS_SPEC_PATH)/styles/compressed $(RUBY_BIN) $(SASS_SPEC_PATH)/sass-spec.rb -c $(SASSC_BIN) --impl libsass \ - --cmd-args "-I $(LIBSASS_SPEC_PATH)/styles/nested -t nested" \ + --cmd-args "-I . -I $(LIBSASS_SPEC_PATH)/styles/nested -t nested" \ --probe-todo $(LOG_FLAGS) $(LIBSASS_SPEC_PATH)/styles/nested test_interactive: $(SASSC_BIN) $(SASS_SPEC_PATH) $(LIBSASS_SPEC_PATH) @@ -345,13 +370,13 @@ test_interactive: $(SASSC_BIN) $(SASS_SPEC_PATH) $(LIBSASS_SPEC_PATH) --cmd-args "-I $(SASS_SPEC_PATH)/$(SASS_SPEC_SPEC_DIR)" \ --interactive $(LOG_FLAGS) $(SASS_SPEC_PATH)/$(SASS_SPEC_SPEC_DIR) $(RUBY_BIN) $(SASS_SPEC_PATH)/sass-spec.rb -c $(SASSC_BIN) --impl libsass \ - --cmd-args "-I $(LIBSASS_SPEC_PATH)/$(LIBSASS_SPEC_SPEC_DIR)" \ + --cmd-args "-I . -I $(LIBSASS_SPEC_PATH)/$(LIBSASS_SPEC_SPEC_DIR)" \ --interactive $(LOG_FLAGS) $(LIBSASS_SPEC_PATH)/$(LIBSASS_SPEC_SPEC_DIR) $(RUBY_BIN) $(SASS_SPEC_PATH)/sass-spec.rb -c $(SASSC_BIN) --impl libsass \ - --cmd-args "-I $(LIBSASS_SPEC_PATH)/styles/compressed -t compressed" \ + --cmd-args "-I . -I $(LIBSASS_SPEC_PATH)/styles/compressed -t compressed" \ --interactive $(LOG_FLAGS) $(LIBSASS_SPEC_PATH)/styles/compressed $(RUBY_BIN) $(SASS_SPEC_PATH)/sass-spec.rb -c $(SASSC_BIN) --impl libsass \ - --cmd-args "-I $(LIBSASS_SPEC_PATH)/styles/nested -t nested" \ + --cmd-args "-I . -I $(LIBSASS_SPEC_PATH)/styles/nested -t nested" \ --interactive $(LOG_FLAGS) $(LIBSASS_SPEC_PATH)/styles/nested clean-objects: | lib diff --git a/Makefile.conf b/Makefile.conf index 679e36ccfa..e1e89e8f97 100644 --- a/Makefile.conf +++ b/Makefile.conf @@ -4,156 +4,226 @@ # in parallel. But we also want to mix them a little too avoid # heavy RAM usage peaks. Other than that the order is arbitrary. - INCFILES = \ sass.h \ - sass2scss.h \ sass/base.h \ - sass/context.h \ - sass/functions.h \ + sass/enums.h \ + sass/error.h \ + sass/fwdecl.h \ + sass/getopt.h \ + sass/import.h \ + sass/importer.h \ + sass/function.h \ + sass/compiler.h \ + sass/variable.h \ + sass/traces.h \ sass/values.h \ sass/version.h HPPFILES = \ ast.hpp \ - ast2c.hpp \ + ast_css.hpp \ + ast_nodes.hpp \ ast_def_macros.hpp \ ast_fwd_decl.hpp \ ast_helpers.hpp \ ast_selectors.hpp \ + ast_containers.hpp \ + ast_imports.hpp \ ast_supports.hpp \ + ast_callable.hpp \ + ast_callables.hpp \ + ast_statements.hpp \ + ast_expressions.hpp \ ast_values.hpp \ + modules.hpp \ backtrace.hpp \ base64vlq.hpp \ - bind.hpp \ - c2ast.hpp \ - check_nesting.hpp \ + character.hpp \ + charcode.hpp \ color_maps.hpp \ constants.hpp \ - context.hpp \ cssize.hpp \ dart_helpers.hpp \ - debug.hpp \ debugger.hpp \ emitter.hpp \ environment.hpp \ - error_handling.hpp \ + exceptions.hpp \ + settings.hpp \ + memory.hpp \ + memory_config.hpp \ + memory_allocator.hpp \ + memory_pool.hpp \ + shared_ptr.hpp \ + MurmurHash2.hpp \ eval.hpp \ - expand.hpp \ extender.hpp \ extension.hpp \ file.hpp \ + import.hpp \ + compiler.hpp \ + fn_meta.hpp \ + fn_maps.hpp \ + fn_math.hpp \ fn_colors.hpp \ fn_lists.hpp \ - fn_maps.hpp \ - fn_miscs.hpp \ - fn_numbers.hpp \ fn_selectors.hpp \ - fn_strings.hpp \ + fn_texts.hpp \ fn_utils.hpp \ + strings.hpp \ inspect.hpp \ + terminal.hpp \ + interpolation.hpp \ + logger.hpp \ json.hpp \ - kwd_arg_macros.hpp \ - lexer.hpp \ - listize.hpp \ + randomize.hpp \ mapping.hpp \ - memory.hpp \ - MurmurHash2.hpp \ - operation.hpp \ - operators.hpp \ ordered_map.hpp \ + source.hpp \ + sources.hpp \ output.hpp \ parser.hpp \ + parser_base.hpp \ + parser_css.hpp \ + parser_expression.hpp \ + parser_media_query.hpp \ + parser_at_root_query.hpp \ + parser_keyframe_selector.hpp \ + parser_sass.hpp \ + parser_scss.hpp \ + parser_selector.hpp \ + parser_stylesheet.hpp \ permutate.hpp \ plugins.hpp \ - position.hpp \ - prelexer.hpp \ + offset.hpp \ + calculation.hpp \ + css_every.hpp \ + css_invisible.hpp \ + sel_any.hpp \ + sel_bogus.hpp \ + sel_useless.hpp \ + sel_invisible.hpp \ remove_placeholders.hpp \ - sass.hpp \ - sass_context.hpp \ - sass_functions.hpp \ - sass_values.hpp \ - settings.hpp \ - source.hpp \ - source_data.hpp \ + capi_sass.hpp \ + capi_error.hpp \ + capi_traces.hpp \ + capi_import.hpp \ + capi_getopt.hpp \ + capi_importer.hpp \ + capi_compiler.hpp \ + capi_variable.hpp \ + capi_function.hpp \ + capi_values.hpp \ + scanner_string.hpp \ source_map.hpp \ + source_state.hpp \ + source_span.hpp \ stylesheet.hpp \ - to_value.hpp \ + preloader.hpp \ units.hpp \ - utf8_string.hpp \ - util.hpp \ - util_string.hpp \ - values.hpp \ - memory/allocator.hpp \ - memory/config.hpp \ - memory/memory_pool.hpp \ - memory/shared_ptr.hpp + unicode.hpp \ + hashing.hpp \ + visitor_css.hpp \ + visitor_expression.hpp \ + visitor_selector.hpp \ + visitor_statement.hpp \ + visitor_value.hpp \ + string_utils.hpp \ + callstack.hpp \ + environment_cnt.hpp \ + environment_key.hpp \ + environment_stack.hpp \ + b64/cencode.hpp \ + b64/encode.hpp SOURCES = \ ast.cpp \ + ast_css.cpp \ + ast_nodes.cpp \ ast_values.cpp \ ast_supports.cpp \ + ast_callables.cpp \ + ast_statements.cpp \ + ast_expressions.cpp \ ast_sel_cmp.cpp \ ast_sel_unify.cpp \ ast_sel_super.cpp \ ast_sel_weave.cpp \ ast_selectors.cpp \ - context.cpp \ + memory_allocator.cpp \ + modules.cpp \ constants.cpp \ - fn_utils.cpp \ - fn_miscs.cpp \ + compiler.cpp \ + fn_meta.cpp \ fn_maps.cpp \ + fn_math.cpp \ fn_lists.cpp \ fn_colors.cpp \ - fn_numbers.cpp \ - fn_strings.cpp \ + fn_texts.cpp \ fn_selectors.cpp \ color_maps.cpp \ environment.cpp \ ast_fwd_decl.cpp \ - bind.cpp \ file.cpp \ - util.cpp \ - util_string.cpp \ + import.cpp \ + string_utils.cpp \ + logger.cpp \ + strings.cpp \ json.cpp \ units.cpp \ - values.cpp \ plugins.cpp \ source.cpp \ - position.cpp \ - lexer.cpp \ - parser.cpp \ - parser_selectors.cpp \ - prelexer.cpp \ + offset.cpp \ + calculation.cpp \ + css_every.cpp \ + css_invisible.cpp \ + sel_any.cpp \ + sel_bogus.cpp \ + sel_useless.cpp \ + sel_invisible.cpp \ eval.cpp \ - eval_selectors.cpp \ - expand.cpp \ - listize.cpp \ + randomize.cpp \ cssize.cpp \ extender.cpp \ extension.cpp \ stylesheet.cpp \ + preloader.cpp \ + interpolation.cpp \ + parser.cpp \ + parser_css.cpp \ + parser_base.cpp \ + parser_scss.cpp \ + parser_sass.cpp \ + parser_selector.cpp \ + parser_stylesheet.cpp \ + parser_expression.cpp \ + parser_media_query.cpp \ + parser_at_root_query.cpp \ + parser_keyframe_selector.cpp \ output.cpp \ inspect.cpp \ + terminal.cpp \ emitter.cpp \ - check_nesting.cpp \ + scanner_string.cpp \ remove_placeholders.cpp \ - sass.cpp \ - sass_values.cpp \ - sass_context.cpp \ - sass_functions.cpp \ - sass2scss.cpp \ - backtrace.cpp \ - operators.cpp \ - ast2c.cpp \ - c2ast.cpp \ - to_value.cpp \ + capi_sass.cpp \ + capi_error.cpp \ + capi_getopt.cpp \ + capi_traces.cpp \ + capi_import.cpp \ + capi_importer.cpp \ + capi_compiler.cpp \ + capi_variable.cpp \ + capi_function.cpp \ + capi_values.cpp \ + character.cpp \ + environment_stack.cpp \ source_map.cpp \ - error_handling.cpp \ - memory/allocator.cpp \ - memory/shared_ptr.cpp \ - utf8_string.cpp \ - base64vlq.cpp + source_state.cpp \ + source_span.cpp \ + exceptions.cpp \ + shared_ptr.cpp \ + unicode.cpp \ + cencode.cpp -CSOURCES = \ - cencode.c +CSOURCES = diff --git a/Readme.md b/Readme.md index 1161465b3a..571848c805 100644 --- a/Readme.md +++ b/Readme.md @@ -1,3 +1,8 @@ +../../test/test.scss --precision 10 +sass-bench\bolt-bench.scss -I sass-bench + +\breeze-gtk\src + LibSass - Sass compiler written in C++ ====================================== diff --git a/appveyor.yml b/appveyor.yml index f462207844..df3060e0f7 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,4 +1,4 @@ -os: Visual Studio 2013 +os: Visual Studio 2015 environment: CTEST_OUTPUT_ON_FAILURE: 1 @@ -8,9 +8,9 @@ environment: - Compiler: msvc Config: Release Platform: Win32 - - Compiler: msvc - Config: Debug - Platform: Win32 +# - Compiler: msvc +# Config: Debug +# Platform: Win32 - Compiler: msvc Config: Release Platform: Win64 @@ -32,8 +32,8 @@ cache: # - 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 + - git clone https://github.com/mgreter/sassc.git --branch refactor/libsass-4-alpha + - git clone https://github.com/mgreter/sass-spec.git --branch refactor/libsass-4-alpha - set PATH=C:\Ruby%ruby_version%\bin;%PATH% - ps: | if(!(gem which minitest 2>$nul)) { gem install minitest --no-ri --no-rdoc } @@ -66,17 +66,6 @@ build_script: test_script: - ps: | $PRNR = $env:APPVEYOR_PULL_REQUEST_NUMBER - if ($PRNR) { - echo "Fetching info for PR $PRNR" - wget https://api.github.com/repos/sass/libsass/pulls/$PRNR -OutFile pr.json - $json = cat pr.json -Raw - $SPEC_PR = [regex]::match($json,'sass\/sass-spec(#|\/pull\/)([0-9]+)').Groups[2].Value - if ($SPEC_PR) { - echo "Checkout sass spec PR $SPEC_PR" - git -C sass-spec fetch -q -u origin pull/$SPEC_PR/head:ci-spec-pr-$SPEC_PR - git -C sass-spec checkout -q --force ci-spec-pr-$SPEC_PR - } - } $env:TargetPath = Join-Path $pwd.Path $env:TargetPath If (Test-Path "$env:TargetPath") { ruby sass-spec/sass-spec.rb --probe-todo --impl libsass -c $env:TargetPath -s sass-spec/spec diff --git a/configure.ac b/configure.ac index b5a943217a..78899d642e 100644 --- a/configure.ac +++ b/configure.ac @@ -88,18 +88,23 @@ the --with-sass-spec-dir= argument. case $sass_spec_dir in /*) SASS_SPEC_PATH=`$RUBY -e "require 'pathname'; puts Pathname.new('$sass_spec_dir').relative_path_from(Pathname.new('$PWD')).to_s"` + SASS_SPEC_ROOT="$sass_spec_dir" ;; *) SASS_SPEC_PATH="$sass_spec_dir" + SASS_SPEC_ROOT="$sass_spec_dir" ;; esac AC_SUBST(SASS_SPEC_PATH) + AC_SUBST(SASS_SPEC_ROOT) else # we do not really need these paths for non test build # but automake may error if we do not define them here SASS_SPEC_PATH=sass-spec + SASS_SPEC_ROOT=sass-spec SASS_SASSC_PATH=sassc AC_SUBST(SASS_SPEC_PATH) + AC_SUBST(SASS_SPEC_ROOT) AC_SUBST(SASS_SASSC_PATH) fi diff --git a/docs/README.md b/docs/README.md index 421393e9dc..e5374cfdac 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,30 +1,24 @@ ## LibSass documentation -LibSass is just a library. To run the code locally (i.e. to compile your -stylesheets), you need an implementer. SassC is an implementer written in C. -There are a number of other implementations of LibSass - for example NodeJS. -We encourage you to write your own port - the whole point of LibSass is that -we want to bring Sass to many other languages! +Welcome to LibSass, the C library to compile your awesome sass style-sheets +to css. LibSass in itself is only a library and doesn't do much on its own +without an implementor. SassC is such an implementer written in C. There are +a plethora of other implementations - for example NodeJS, python or perl. +We encourage you to write your own - the whole point of LibSass is that we +want to bring Sass to many other languages! -## LibSass road-map +## LibSass road-map 2021 -Since ruby-sass was retired in 2019 in favor of dart-sass, we slowly move -toward full compatibility with the latest Sass specifications, although -features like the module `@use` system may take a bit longer to add. +Since ruby-sass was retired in 2019 in favor of dart-sass, we are slowly +moving toward full compatibility with the latest Sass specifications. ### Implementing LibSass -If you're interested in implementing LibSass in your own project see the -[API Documentation](api-doc.md) which now includes implementing your own -[Sass functions](api-function.md). You may wish to [look at other +If you're interested in implementing LibSass in your own project, see the +[API Documentation](api-doc.md) which now include implementing your own +[Sass functions](api-function.md). Or you may wish to [look at other implementations](implementations.md) for your language of choice. -### Contributing to LibSass - -| 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](https://sass-lang.com/community-guidelines). Be nice and everyone else will be nice too! | - ### Building LibSass Please refer to the steps on [Building LibSass](build.md) @@ -32,3 +26,8 @@ Please refer to the steps on [Building LibSass](build.md) ### Developing LibSass Please refer to [Developing LibSass](developing.md) + +### Community Guidelines + +See [Sass Community Guidelines](https://sass-lang.com/community-guidelines). +Be nice and everyone else will be nice too! diff --git a/docs/api-basics.md b/docs/api-basics.md new file mode 100644 index 0000000000..d37c5c4b23 --- /dev/null +++ b/docs/api-basics.md @@ -0,0 +1,87 @@ +## API documentation basics + +LibSass is C library, written in C++ with a C-API to link against. +It can either be statically included are linked against a system +wide installed shared library. The complete C-API surface is implemented +via functions and by passing around anonymous structures. The implementor +does not know any implementation detail about the objects passed around. + +### C-API headers + +You may include specific headers directly, which have been split into +groups as we saw fit, or you can simply include the main `sass.h` header. + +### List of C-API headers + +- base.h - Includes and defines basic stuff. +- fwdecl.h - Forward declares all known anonymous structures. +- enums.h - Enums for e.g. config options exposed on the C-API. +- error.h - C-API functions to access `SassError` structs. +- import.h - C-API functions to access `SassImport` structs. +- importer.h - C-API functions to access `SassImporter` structs. +- traces.h - C-API functions to access `SassTrace` structs. +- values.h - C-API functions to access `SassValue` structs. +- variable.h - C-API functions to access scoped variables. +- version.h - Header including hardcoded LibSass version. + +## Anonymous structure pointers + +LibSass returns and consumes anonymous structure pointers on its C-API. +Internally those are often directly mapped to an instance of a C++ object. +Since we don't want implementors to know anything about the implementation +details, we forward declare named structs (e.g. `SassCompiler`), but never +provide any implementation for it. LibSass will simply cast a pointer from +the c++ side to the anonymous struct and relies on the C-API to pass it +back as expected to the corresponding functions. There the pointer will +be statically cast back to the actual implementation. Since we provide +a unique anonymous struct for every internal type, this should be safe as +compilers should catch the case when arguments mismatch on the C-API side. + +### List of anonymous structures + +These structs have no real implementation and are only passed around as pointers. +Internally in LibSass these pointers represent mostly different c++ objects. + +- `SassError` - An error object holding further information +- `SassTrace` - An single stack-trace object holding further information +- `SassSource` - Imported source with associated import and resolved path. +- `SassSrcSpan` - Parser state for column, line and source information. +- `SassCompiler` - Main object to hold state for whole compilation phase. +- `SassFunction` - Custom function object holding callback and cookie. +- `SassImport` - Single import for entry point or returned by custom importer. +- `SassImporter` - Custom importer function to be hooked into our loading. +- `SassImportList` - Custom importers can return a SassImport list. +- `SassMapIterator` - Object to support iteration API over map objects. + +## Why using c++11 (or gcc4.4 compatibility) + +Since LibSass 3.0 we started to use more and more c++11 features. Some just +crept in, others were utilized deliberately. With LibSass 4.0 I took the +decision to fully utilize whatever c++11 could offer. The main reason to +fully embrace c++11 is the move semantics it brings. Earlier we also tried +to support compilers that only had partial c++11 support (e.g. gnu++0x). +With LibSass 4.0 we don't really support this target anymore, as any compiler +not older than 5 years should support the c++11 syntax we use. + +Note: currently the LibSass 4.0 release is on going and the final +target compiler is gcc 4.8, as it should fully support c++11. +More testing and tuning needs to be done after a 4.0 release! + +## Binary distributions and linkage issues + +LibSass itself does not have any official binary distribution. The main reason for +this is because it is nearly impossible to do so reliably for each and every +thinkable operating system. Since LibSass is written in c++ we e.g. depend on the +compiler c++ runtime library. On windows this problem is a bit less problematic, +and there is a [semi-official installer][1] for it. But on Linux this e.g. is also +depending on the used libc library. Since linux offers a choice here, a binary +distribution compiled with glibc may not be compatible on a system that uses musl, +or a compilation with latest glibc may not be compatible with older glibc versions. + +By now LibSass is readily available on a lot of linux systems by their internal +packet managers, so please try to install it that way. Alternatively try to install +a recent compiler (e.g. gcc or clang) and compile it from source, preferably via the +autotools way, to ensure correct linkage with tools that link against system wide +installed LibSass (GNU coding style). + +[1]: http://libsass.ocbnet.ch/installer/ diff --git a/docs/api-compiler-example.md b/docs/api-compiler-example.md new file mode 100644 index 0000000000..e6b497811d --- /dev/null +++ b/docs/api-compiler-example.md @@ -0,0 +1,125 @@ +## Example with data import + +```C:data.c +#include +#include + +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 compiler object holding config and states + struct SassCompiler* compiler = sass_make_compiler(); + // we've set the input name to "styles" here, which means ... + struct SassImport* data = sass_make_content_import(text, "styles"); + // ... we can't deduct the type automatically from the extension. + sass_import_set_syntax(data, SASS_IMPORT_SCSS); + // each compiler must have exactly one entry point + sass_compiler_set_entry_point(compiler, data); + // entry point now passed to compiler, so its reference count was increased + // in order to not leak memory we must release our own usage (usage after is UB) + sass_delete_import(data); // decrease ref-count + + // Execute all three phases + sass_compiler_parse(compiler); + sass_compiler_compile(compiler); + sass_compiler_render(compiler); + + // Print any warning to console + if (sass_compiler_get_warn_string(compiler)) { + sass_print_stderr(sass_compiler_get_warn_string(compiler)); + } + + // Print error message if we have an error + if (sass_compiler_get_status(compiler) != 0) { + const struct SassError* error = sass_compiler_get_error(compiler); + sass_print_stderr(sass_error_get_string(error)); + } + + // Get result code after all compilation steps + int status = sass_compiler_get_status(compiler); + + // Write to output if no errors occurred + if (status == 0) { + + // Check if config tells us to write some files + bool writeOutput = sass_compiler_has_output_file(compiler); + bool writeSrcMap = sass_compiler_has_srcmap_file(compiler); + // Paths where to write stuff to (might be `stream://stdout`) + const char* outfile = sass_compiler_get_output_path(compiler); + const char* mapfile = sass_compiler_get_srcmap_path(compiler); + // Get the parts to be added to the output file (or stdout) + const char* content = sass_compiler_get_output_string(compiler); + const char* footer = sass_compiler_get_footer_string(compiler); + const char* srcmap = sass_compiler_get_srcmap_string(compiler); + + // Output all results + if (content) puts(content); + if (footer) puts(footer); + + } + + // 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 import + +```C:file.c +#include +#include + +int main(int argc, const char* argv[]) +{ + + // create compiler object holding config and states + struct SassCompiler* compiler = sass_make_compiler(); + // set input name with extension so we can deduct type from it + struct SassImport* import = sass_make_file_import("foo.scss"); + // each compiler must have exactly one entry point + sass_compiler_set_entry_point(compiler, import); + // entry point now passed to compiler, so its reference count was increased + // in order to not leak memory we must release our own usage (usage after is UB) + sass_delete_import(import); // decrease ref-count + + // Execute compiler and print/write results + sass_compiler_execute(compiler, false); + + // Get result code after all compilation steps + int status = sass_compiler_get_status(compiler); + + // Clean-up compiler, we're done + sass_delete_compiler(compiler); + + // exit status + return status; + +} +``` + +### Compile file.c + +```bash +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/docs/api-compiler.md b/docs/api-compiler.md new file mode 100644 index 0000000000..f7a3f179a2 --- /dev/null +++ b/docs/api-compiler.md @@ -0,0 +1,233 @@ +## Sass Compiler API + +Sass Compiler is the main object to facilitate the Sass compilation process. +It holds the configuration, the compilation state and the results. It's the +main object you will interact on the C-API side. + +The regular life-cycle of a compiler will mostly look like this: +- Create new compiler object +- Set various configuration options +- Set one compilation entry point +- Call the parse function +- Call the compile function +- Call the render function +- Process the results + +The split between parse, compile and render is done because there +are cases where we might want to omit certain phases. One example +would be to only execute the init phase for a watcher to get all +includes first, before triggering the first compilation. + +### Compiler entry point + +Every compiler must have one entry point. This can either be a file or +a data entry point. See [import api][api-import.md] for further details. + +### Writing output files + +Implementors may write output files on their own or you can tell +LibSass to write them to the configured locations. The functions +`sass_compiler_write_output` and `sass_compiler_write_srcmap` should +act according to the configuration and the produced results. + +### Logger configuration + +LibSass supports error reporting with ANSI colors on windows and unix. It can +also make use of Unicode compatible terminals. There is some basic auto-detection +you can trigger by calling `sass_compiler_autodetect_logger_capabilities`. You +can still overload the detected settings afterwards. Additionally LibSass will +try to break running-text into the available columns and shorten stack-traces. + +### Example code + +See [sass compiler code example](api-compiler-example.md). + +### Basic Usage + +```C +#include +``` + +### Sass Compiler API + +```C +///////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////// + +// Create a new LibSass compiler context +struct SassCompiler* sass_make_compiler(); + +// Release all memory allocated with the compiler +void sass_delete_compiler(struct SassCompiler* compiler); + +///////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////// + +// Parse the entry point and potentially all imports within. +void sass_compiler_parse(struct SassCompiler* compiler); + +// Evaluate the parsed entry point and store resulting ast-tree. +void sass_compiler_compile(struct SassCompiler* compiler); + +// Render the evaluated ast-tree to get the final output string. +void sass_compiler_render(struct SassCompiler* compiler); + +// Write or print the output to the console or the configured output path +void sass_compiler_write_output(struct SassCompiler* compiler); + +// Write source-map to configured path if options are set accordingly +void sass_compiler_write_srcmap(struct SassCompiler* compiler); + +// Execute all compiler steps and write/print results +int sass_compiler_execute(struct SassCompiler* compiler, bool quiet); + +///////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////// + +// Add additional include paths where LibSass will look for includes. +// Note: the passed in `paths` can be path separated (`;` on windows, `:` otherwise). +void sass_compiler_add_include_paths(struct SassCompiler* compiler, const char* paths); + +// Load dynamic loadable plugins from `paths`. Plugins are only supported on certain OSs and +// are still in experimental state. This will look for `*.dll`, `*.so` or `*.dynlib` files. +// It then tries to load the found libraries and does a few checks to see if the library +// is actually a LibSass plugin. We then call its init hook if the library is compatible. +// Note: the passed in `paths` can be path separated (`;` on windows, `:` otherwise). +void sass_compiler_load_plugins(struct SassCompiler* compiler, const char* paths); + +// Add a custom header importer that will always be executed before any other +// compilations takes place. Useful to prepend a shared copyright header or to +// provide global variables or functions. This feature is still in experimental state. +// Note: With the adaption of Sass Modules this might be completely replaced in the future. +void sass_compiler_add_custom_header(struct SassCompiler* compiler, struct SassImporter* header); + +// Add a custom importer that will be executed when a sass `@import` rule is found. +// This is useful to e.g. rewrite import locations or to load content from remote. +// For more please check https://github.com/sass/libsass/blob/master/docs/api-importer.md +// Note: The importer will not be called for regular css `@import url()` rules. +void sass_compiler_add_custom_importer(struct SassCompiler* compiler, struct SassImporter* importer); + +// Add a custom function that will be executed when the corresponding function call is +// requested from any sass code. This is useful to provide custom functions in your code. +// For more please check https://github.com/sass/libsass/blob/master/docs/api-function.md +void sass_compiler_add_custom_function(struct SassCompiler* compiler, struct SassFunction* function); + +///////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////// + +// Setter for output style (see `enum SassOutputStyle` for possible options). +void sass_compiler_set_output_style(struct SassCompiler* compiler, enum SassOutputStyle output_style); + +// Try to detect and set logger options for terminal colors, unicode and columns. +void sass_compiler_autodetect_logger_capabilities(struct SassCompiler* compiler); + +// Setter for enabling/disabling logging with ANSI colors. +void sass_compiler_set_logger_colors(struct SassCompiler* compiler, bool enable); + +// Setter for enabling/disabling logging with unicode text. +void sass_compiler_set_logger_unicode(struct SassCompiler* compiler, bool enable); + +// Getter for number precision (how floating point numbers are truncated). +int sass_compiler_get_precision(struct SassCompiler* compiler); + +// Setter for number precision (how floating point numbers are truncated). +void sass_compiler_set_precision(struct SassCompiler* compiler, int precision); + +// Getter for compiler entry point (which file or data to parse first). +struct SassImport* sass_compiler_get_entry_point(struct SassCompiler* compiler); + +// Setter for compiler entry point (which file or data to parse first). +void sass_compiler_set_entry_point(struct SassCompiler* compiler, struct SassImport* import); + +// Getter for compiler output path (where to store the result) +// Note: LibSass does not write the file, implementers should write to this path. +const char* sass_compiler_get_output_path(struct SassCompiler* compiler); + +// Setter for compiler output path (where to store the result) +// Note: LibSass does not write the file, implementers should write to this path. +void sass_compiler_set_output_path(struct SassCompiler* compiler, const char* output_path); + +///////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////// + +// Getter for warnings that occurred during any step. +const char* sass_compiler_get_warn_string(struct SassCompiler* compiler); + +// Getter for output after parsing, compilation and rendering. +const char* sass_compiler_get_output_string(struct SassCompiler* compiler); + +// Getter for footer string containing optional source-map (embedded or link). +const char* sass_compiler_get_footer_string(struct SassCompiler* compiler); + +// Getter for string containing the optional source-mapping. +const char* sass_compiler_get_srcmap_string(struct SassCompiler* compiler); + +// Check if implementor is expected to write a output file +bool sass_compiler_has_output_file(struct SassCompiler* compiler); + +// Check if implementor is expected to write a source-map file +bool sass_compiler_has_srcmap_file(struct SassCompiler* compiler); + +///////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////// + +// Setter for source-map mode (how to embed or not embed the source-map). +void sass_compiler_set_srcmap_mode(struct SassCompiler* compiler, enum SassSrcMapMode mode); + +// Setter for source-map path (where to store the source-mapping). +// Note: if path is not explicitly given, we will deduct one from output path. +// Note: LibSass does not write the file, implementers should write to this path. +void sass_compiler_set_srcmap_path(struct SassCompiler* compiler, const char* path); + +// Getter for source-map path (where to store the source-mapping). +// Note: if path is not explicitly given, we will deduct one from output path. +// Note: the value will only be deducted after the main render phase is completed. +// Note: LibSass does not write the file, implementers should write to this path. +const char* sass_compiler_get_srcmap_path(struct SassCompiler* compiler); + +// Setter for source-map root (simply passed to the resulting srcmap info). +// Note: if not given, no root attribute will be added to the srcmap info object. +void sass_compiler_set_srcmap_root(struct SassCompiler* compiler, const char* root); + +// Setter for source-map file-url option (renders urls in srcmap as `file://` urls) +void sass_compiler_set_srcmap_file_urls(struct SassCompiler* compiler, bool enable); + +// Setter for source-map embed-contents option (includes full sources in the srcmap info) +void sass_compiler_set_srcmap_embed_contents(struct SassCompiler* compiler, bool enable); + +///////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////// + +// Getter to return the number of all included files. +size_t sass_compiler_get_included_files_count(struct SassCompiler* compiler); + +// Getter to return path to the included file at position `n`. +const char* sass_compiler_get_included_file_path(struct SassCompiler* compiler, size_t n); + +///////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////// + +// Getter for current import context. Use `SassImport` functions to query the state. +const struct SassImport* sass_compiler_get_last_import(struct SassCompiler* compiler); + +// Returns pointer to error object associated with compiler. +// Will be valid until the associated compiler is destroyed. +const struct SassError* sass_compiler_get_error(struct SassCompiler* compiler); + +// Returns status code for compiler (0 meaning success, anything else is an error) +int sass_compiler_get_status(struct SassCompiler* compiler); + +///////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////// + +// Resolve a file relative to last import or include paths in the sass option struct. +char* sass_compiler_find_file(const char* path, struct SassCompiler* compiler); + +// Resolve an include relative to last import or include paths in the sass option struct. +// This will do a lookup as LibSass would do internally (partials, different extensions). +// ToDo: Check if we should add `includeIndex` option to check for directory index files!? +char* sass_compiler_find_include(const char* path, struct SassCompiler* compiler); + +///////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////// +``` diff --git a/docs/api-context-example.md b/docs/api-context-example.md deleted file mode 100644 index 97585a8b37..0000000000 --- a/docs/api-context-example.md +++ /dev/null @@ -1,94 +0,0 @@ -## Example for `data_context` - -```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" - -int main( int argc, const char* argv[] ) -{ - - // get the input file from first argument or use default - const char* input = argc > 1 ? argv[1] : "styles.scss"; - - // create the file context and get all related structs - struct Sass_File_Context* file_ctx = sass_make_file_context(input); - struct Sass_Context* ctx = sass_file_context_get_context(file_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_file_context(file_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_file_context(file_ctx); - - // exit status - return status; - -} -``` - -### Compile file.c - -```bash -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/docs/api-context-internal.md b/docs/api-context-internal.md deleted file mode 100644 index 901b277c1f..0000000000 --- a/docs/api-context-internal.md +++ /dev/null @@ -1,163 +0,0 @@ -```C -// Input behaviours -enum Sass_Input_Style { - SASS_CONTEXT_NULL, - SASS_CONTEXT_FILE, - SASS_CONTEXT_DATA, - SASS_CONTEXT_FOLDER -}; - -// sass config options structure -struct Sass_Inspect_Options { - - // Output style for the generated css code - // A value from above SASS_STYLE_* constants - enum Sass_Output_Style output_style; - - // Precision for fractional numbers - int precision; - -}; - -// sass config options structure -struct Sass_Output_Options : Sass_Inspect_Options { - - // String to be used for indentation - const char* indent; - // String to be used to for line feeds - const char* linefeed; - - // Emit comments in the generated CSS indicating - // the corresponding source line. - bool source_comments; - -}; - -// sass config options structure -struct Sass_Options : Sass_Output_Options { - - // embed sourceMappingUrl as data uri - bool source_map_embed; - - // embed include contents in maps - bool source_map_contents; - - // create file urls for sources - bool source_map_file_urls; - - // Disable sourceMappingUrl in css output - bool omit_source_map_url; - - // Treat source_string as sass (as opposed to scss) - bool is_indented_syntax_src; - - // The input path is used for source map - // generation. It can be used to define - // something with string compilation or to - // overload the input file path. It is - // set to "stdin" for data contexts and - // to the input file on file contexts. - char* input_path; - - // The output path is used for source map - // generation. LibSass will not write to - // this file, it is just used to create - // information in source-maps etc. - char* output_path; - - // Colon-separated list of paths - // Semicolon-separated on Windows - // Maybe use array interface instead? - char* include_path; - char* plugin_path; - - // Include paths (linked string list) - struct string_list* include_paths; - // Plugin paths (linked string list) - struct string_list* plugin_paths; - - // Path to source map file - // Enables source map generation - // Used to create sourceMappingUrl - char* source_map_file; - - // Directly inserted in source maps - char* source_map_root; - - // Custom functions that can be called from sccs code - Sass_Function_List c_functions; - - // Callback to overload imports - Sass_Importer_List c_importers; - - // List of custom headers - Sass_Importer_List c_headers; - -}; - -// base for all contexts -struct Sass_Context : Sass_Options -{ - - // store context type info - enum Sass_Input_Style type; - - // generated output data - char* output_string; - - // generated source map json - char* source_map_string; - - // error status - int error_status; - char* error_json; - char* error_text; - char* error_message; - // error position - char* error_file; - size_t error_line; - size_t error_column; - char* error_src; - - // report imported files - char** included_files; - -}; - -// struct for file compilation -struct Sass_File_Context : Sass_Context { - - // no additional fields required - // input_path is already on options - -}; - -// struct for data compilation -struct Sass_Data_Context : Sass_Context { - - // provided source string - char* source_string; - char* srcmap_string; - -}; - -// Compiler states -enum Sass_Compiler_State { - SASS_COMPILER_CREATED, - SASS_COMPILER_PARSED, - SASS_COMPILER_EXECUTED -}; - -// link c and cpp context -struct Sass_Compiler { - // progress status - Sass_Compiler_State state; - // original c context - Sass_Context* c_ctx; - // Sass::Context - Sass::Context* cpp_ctx; - // Sass::Block - Sass::Block_Obj root; -}; -``` - diff --git a/docs/api-context.md b/docs/api-context.md index e101da3082..c071db8800 100644 --- a/docs/api-context.md +++ b/docs/api-context.md @@ -1,298 +1,2 @@ -Sass Contexts come in two flavors: - -- `Sass_File_Context` -- `Sass_Data_Context` - -### Basic Usage - -```C -#include "sass/context.h" -``` - -***Sass_Options*** - -```C -// Precision for fractional numbers -int precision; -``` -```C -// Output style for the generated css code -// A value from above SASS_STYLE_* constants -int output_style; -``` -```C -// Emit comments in the generated CSS indicating -// the corresponding source line. -bool source_comments; -``` -```C -// embed sourceMappingUrl as data uri -bool source_map_embed; -``` -```C -// embed include contents in maps -bool source_map_contents; -``` -```C -// create file urls for sources -bool source_map_file_urls; -``` -```C -// Disable sourceMappingUrl in css output -bool omit_source_map_url; -``` -```C -// Treat source_string as sass (as opposed to scss) -bool is_indented_syntax_src; -``` -```C -// The input path is used for source map -// generating. It can be used to define -// something with string compilation or to -// overload the input file path. It is -// set to "stdin" for data contexts and -// to the input file on file contexts. -char* input_path; -``` -```C -// The output path is used for source map -// generating. LibSass will not write to -// this file, it is just used to create -// information in source-maps etc. -char* output_path; -``` -```C -// String to be used for indentation -const char* indent; -``` -```C -// String to be used to for line feeds -const char* linefeed; -``` -```C -// Colon-separated list of paths -// Semicolon-separated on Windows -char* include_path; -char* plugin_path; -``` -```C -// Additional include paths -// Must be null delimited -char** include_paths; -char** plugin_paths; -``` -```C -// Path to source map file -// Enables the source map generating -// Used to create sourceMappingUrl -char* source_map_file; -``` -```C -// Directly inserted in source maps -char* source_map_root; -``` -```C -// Custom functions that can be called from Sass code -Sass_C_Function_List c_functions; -``` -```C -// Callback to overload imports -Sass_C_Import_Callback importer; -``` - -***Sass_Context*** - -```C -// store context type info -enum Sass_Input_Style type; -```` -```C -// generated output data -char* output_string; -``` -```C -// generated source map json -char* source_map_string; -``` -```C -// error status -int error_status; -char* error_json; -char* error_text; -char* error_message; -// error position -char* error_file; -size_t error_line; -size_t error_column; -char* error_src; -``` -```C -// report imported files -char** included_files; -``` - -***Sass_File_Context*** - -```C -// no additional fields required -// input_path is already on options -``` - -***Sass_Data_Context*** - -```C -// provided source string -char* source_string; -``` - -### Sass Context API - -```C -// Forward declaration -struct Sass_Compiler; - -// Forward declaration -struct Sass_Options; -struct Sass_Context; // : Sass_Options -struct Sass_File_Context; // : Sass_Context -struct Sass_Data_Context; // : Sass_Context - -// Create and initialize an option struct -struct Sass_Options* sass_make_options (void); -// Create and initialize a specific context -struct Sass_File_Context* sass_make_file_context (const char* input_path); -struct Sass_Data_Context* sass_make_data_context (char* source_string); - -// Call the compilation step for the specific context -int sass_compile_file_context (struct Sass_File_Context* ctx); -int sass_compile_data_context (struct Sass_Data_Context* ctx); - -// Create a sass compiler instance for more control -struct Sass_Compiler* sass_make_file_compiler (struct Sass_File_Context* file_ctx); -struct Sass_Compiler* sass_make_data_compiler (struct Sass_Data_Context* data_ctx); - -// Execute the different compilation steps individually -// Useful if you only want to query the included files -int sass_compiler_parse (struct Sass_Compiler* compiler); -int sass_compiler_execute (struct Sass_Compiler* compiler); - -// Release all memory allocated with the compiler -// This does _not_ include any contexts or options -void sass_delete_compiler (struct Sass_Compiler* compiler); -void sass_delete_options(struct Sass_Options* options); - -// Release all memory allocated and also ourself -void sass_delete_file_context (struct Sass_File_Context* ctx); -void sass_delete_data_context (struct Sass_Data_Context* ctx); - -// Getters for Context from specific implementation -struct Sass_Context* sass_file_context_get_context (struct Sass_File_Context* file_ctx); -struct Sass_Context* sass_data_context_get_context (struct Sass_Data_Context* data_ctx); - -// Getters for Context_Options from Sass_Context -struct Sass_Options* sass_context_get_options (struct Sass_Context* ctx); -struct Sass_Options* sass_file_context_get_options (struct Sass_File_Context* file_ctx); -struct Sass_Options* sass_data_context_get_options (struct Sass_Data_Context* data_ctx); -void sass_file_context_set_options (struct Sass_File_Context* file_ctx, struct Sass_Options* opt); -void sass_data_context_set_options (struct Sass_Data_Context* data_ctx, struct Sass_Options* opt); - -// Getters for Sass_Context values -const char* sass_context_get_output_string (struct Sass_Context* ctx); -int sass_context_get_error_status (struct Sass_Context* ctx); -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); -char** sass_context_get_included_files (struct Sass_Context* ctx); - -// Getters for Sass_Compiler options (query import stack) -size_t sass_compiler_get_import_stack_size(struct Sass_Compiler* compiler); -Sass_Import_Entry sass_compiler_get_last_import(struct Sass_Compiler* compiler); -Sass_Import_Entry sass_compiler_get_import_entry(struct Sass_Compiler* compiler, size_t idx); -// Getters for Sass_Compiler options (query function stack) -size_t sass_compiler_get_callee_stack_size(struct Sass_Compiler* compiler); -Sass_Callee_Entry sass_compiler_get_last_callee(struct Sass_Compiler* compiler); -Sass_Callee_Entry sass_compiler_get_callee_entry(struct Sass_Compiler* compiler, size_t idx); - -// Take ownership of memory (value on context is set to 0) -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); -``` - -### Sass Options API - -```C -// Getters for Context_Option values -int sass_option_get_precision (struct Sass_Options* options); -enum Sass_Output_Style sass_option_get_output_style (struct Sass_Options* options); -bool sass_option_get_source_comments (struct Sass_Options* options); -bool sass_option_get_source_map_embed (struct Sass_Options* options); -bool sass_option_get_source_map_contents (struct Sass_Options* options); -bool sass_option_get_source_map_file_urls (struct Sass_Options* options); -bool sass_option_get_omit_source_map_url (struct Sass_Options* options); -bool sass_option_get_is_indented_syntax_src (struct Sass_Options* options); -const char* sass_option_get_indent (struct Sass_Options* options); -const char* sass_option_get_linefeed (struct Sass_Options* options); -const char* sass_option_get_input_path (struct Sass_Options* options); -const char* sass_option_get_output_path (struct Sass_Options* options); -const char* sass_option_get_source_map_file (struct Sass_Options* options); -const char* sass_option_get_source_map_root (struct Sass_Options* options); -Sass_C_Function_List sass_option_get_c_functions (struct Sass_Options* options); -Sass_C_Import_Callback sass_option_get_importer (struct Sass_Options* options); - -// Getters for Context_Option include path array -size_t sass_option_get_include_path_size(struct Sass_Options* options); -const char* sass_option_get_include_path(struct Sass_Options* options, size_t i); -// Plugin paths to load dynamic libraries work the same -size_t sass_option_get_plugin_path_size(struct Sass_Options* options); -const char* sass_option_get_plugin_path(struct Sass_Options* options, size_t i); - -// Setters for Context_Option values -void sass_option_set_precision (struct Sass_Options* options, int precision); -void sass_option_set_output_style (struct Sass_Options* options, enum Sass_Output_Style output_style); -void sass_option_set_source_comments (struct Sass_Options* options, bool source_comments); -void sass_option_set_source_map_embed (struct Sass_Options* options, bool source_map_embed); -void sass_option_set_source_map_contents (struct Sass_Options* options, bool source_map_contents); -void sass_option_set_source_map_file_urls (struct Sass_Options* options, bool source_map_file_urls); -void sass_option_set_omit_source_map_url (struct Sass_Options* options, bool omit_source_map_url); -void sass_option_set_is_indented_syntax_src (struct Sass_Options* options, bool is_indented_syntax_src); -void sass_option_set_indent (struct Sass_Options* options, const char* indent); -void sass_option_set_linefeed (struct Sass_Options* options, const char* linefeed); -void sass_option_set_input_path (struct Sass_Options* options, const char* input_path); -void sass_option_set_output_path (struct Sass_Options* options, const char* output_path); -void sass_option_set_plugin_path (struct Sass_Options* options, const char* plugin_path); -void sass_option_set_include_path (struct Sass_Options* options, const char* include_path); -void sass_option_set_source_map_file (struct Sass_Options* options, const char* source_map_file); -void sass_option_set_source_map_root (struct Sass_Options* options, const char* source_map_root); -void sass_option_set_c_functions (struct Sass_Options* options, Sass_C_Function_List c_functions); -void sass_option_set_importer (struct Sass_Options* options, Sass_C_Import_Callback importer); - -// Push function for paths (no manipulation support for now) -void sass_option_push_plugin_path (struct Sass_Options* options, const char* path); -void sass_option_push_include_path (struct Sass_Options* options, const char* path); - -// Resolve a file via the given include paths in the sass option struct -// find_file looks for the exact file name while find_include does a regular sass include -char* sass_find_file (const char* path, struct Sass_Options* opt); -char* sass_find_include (const char* path, struct Sass_Options* opt); - -// Resolve a file relative to last import or include paths in the sass option struct -// find_file looks for the exact file name while find_include does a regular sass include -char* sass_compiler_find_file (const char* path, struct Sass_Compiler* compiler); -char* sass_compiler_find_include (const char* path, struct Sass_Compiler* compiler); -``` - -### More links - -- [Sass Context Example](api-context-example.md) -- [Sass Context Internal](api-context-internal.md) - +Context was moved to compiler. +- [See compiler documentation](api-compiler.md) diff --git a/docs/api-doc.md b/docs/api-doc.md index 5e8ad7962c..461e73bc42 100644 --- a/docs/api-doc.md +++ b/docs/api-doc.md @@ -1,29 +1,41 @@ ## Introduction -LibSass wouldn't be much good without a way to interface with it. These -interface documentations describe the various functions and data structures -available to implementers. They are split up over three major components, which -have all their own source files (plus some common functionality). - -- [Sass Context](api-context.md) - Trigger and handle the main Sass compilation -- [Sass Value](api-value.md) - Exchange values and its format with LibSass -- [Sass Function](api-function.md) - Get invoked by LibSass for function statments -- [Sass Importer](api-importer.md) - Get invoked by LibSass for @import statments +LibSass C-API is designed as a functional API; every access on the C-API is a +function call. This has a few drawbacks, but a lot of desirable characteristics. +The most important one is that it reduces the API surface to simple function calls. +Implementors do not need to know how internal structures look, so we are free +to adjust them as we see fit, as long as the functional API stays the same. + +Under the hood, LibSass uses advanced C++ code (c++11 being the current target), so +you will need a modern compiler and also link against c++ runtime libraries. This poses +some issues in regard of portability and deployment of precompiled binary distributions. + +The API has been split into a few categories which come with their own documentation: + +- [Sass Basics](api-basics.md) - Further details and information about the C-API +- [Sass Compiler](api-compiler.md) - Trigger and handle the main Sass compilation +- [Sass Imports](api-import.md) - Imports to be loaded and parsed by LibSass +- [Sass Values](api-value.md) - Exchange Sass values between LibSass and implementors +- [Sass Functions](api-function.md) - Get invoked by LibSass for function statements +- [Sass Importers](api-importer.md) - Get invoked by LibSass for @import statements +- [Sass Variables](api-variable.md) - Query or update existing scope variables +- [Sass Traces](api-traces.md) - Access to traces for debug information +- [Sass Error](api-error.md) - Access to errors for debug information ### Basic usage First you will need to include the header file! -This will automatically load all other headers too! +This will automatically load all LibSass headers! ```C -#include "sass/context.h" +#include ``` ## Basic C Example ```C #include -#include "sass/context.h" +#include int main() { puts(libsass_version()); @@ -31,147 +43,148 @@ int main() { } ``` +### Compile and link against LibSass + ```bash gcc -Wall version.c -lsass -o version && ./version ``` -## More C Examples +## More code examples -- [Sample code for Sass Context](api-context-example.md) -- [Sample code for Sass Value](api-value-example.md) +- [Sample code for Sass Compiler](api-compiler-example.md) - [Sample code for Sass Function](api-function-example.md) - [Sample code for Sass Importer](api-importer-example.md) +- [Sample code for Sass Value](api-value-example.md) -## Compiling your code - -The most important is your sass file (or string of sass code). With this, you -will want to start a LibSass compiler. Here is some pseudocode describing the -process. The compiler has two different modes: direct input as a string with -`Sass_Data_Context` or LibSass will do file reading for you by using -`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") - options = sass_file_context_get_options(context) - sass_option_set_precision(options, 1) - sass_option_set_source_comments(options, true) - - sass_file_context_set_options(context, options) - - compiler = sass_make_file_compiler(sass_context) - sass_compiler_parse(compiler) - sass_compiler_execute(compiler) - - output = sass_context_get_output_string(context) - // Retrieve errors during compilation - error_status = sass_context_get_error_status(context) - json_error = sass_context_get_error_json(context) - // Release memory dedicated to the C compiler - sass_delete_compiler(compiler) - -**Building a data compiler** - - // 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) - - sass_data_context_set_options(context, options) - - compiler = sass_make_data_compiler(context) - sass_compiler_parse(compiler) - sass_compiler_execute(compiler) - - output = sass_context_get_output_string(context) - // div a { color: blue; } - // Retrieve errors during compilation - error_status = sass_context_get_error_status(context) - json_error = sass_context_get_error_json(context) - // Release memory dedicated to the C compiler - sass_delete_compiler(compiler) - -## Sass Context Internals - -Everything is stored in structs: - -```C -struct Sass_Options; -struct Sass_Context : Sass_Options; -struct Sass_File_context : Sass_Context; -struct Sass_Data_context : Sass_Context; -``` - -This mirrors very well how `libsass` uses these structures. - -- `Sass_Options` holds everything you feed in before the compilation. It also hosts -`input_path` and `output_path` options, because they are used to generate/calculate -relative links in source-maps. The `input_path` is shared with `Sass_File_Context`. -- `Sass_Context` holds all the data returned by the compilation step. -- `Sass_File_Context` is a specific implementation that requires no additional fields -- `Sass_Data_Context` is a specific implementation that adds the `input_source` field - -Structs can be down-casted to access `context` or `options`! +## Compiler entry points + +LibSass parsing starts with an entry point, which can either be a +file or some text you provide directly. Relative includes are +resolved against the current (virtual) working directory. Entry +points must be of type `struct SassImport*` and can be created +via different constructor functions: + +- `sass_make_file_import("styles.scss"); // loads the file` +- `sass_make_stdin_import("styles.scss"); // reads from stdin` +- `sass_make_content_import(text, "styles.scss"); // uses text` +- `sass_make_import(imp_path, abs_path, source, srcmap, format);` + +Remember that `SassImport` must be freed via `sass_delete_import`. + +**Building a compiler** + + // Create the main compiler object + struct SassCompiler* compiler = sass_make_compiler(); + // Check terminal capabilities (useful for CLI tools) + sass_compiler_autodetect_logger_capabilities(compiler); + // Create a file-import entry point (without input file-name) + struct SassImport* import = sass_make_content_import("foo{bar:baz}", 0); + // Set import syntax since input has no file extension + sass_import_set_syntax(import, SASS_IMPORT_SCSS); + // Tell compiler which entry point to load + sass_compiler_set_entry_point(compiler, import); + // We are done with this (ref-counted) import + sass_delete_import(import); + // Execute compiler and print/write results + sass_compiler_execute(compiler, false); + // Get result code after all compilation steps + int status = sass_compiler_get_status(compiler); + // Clean-up compiler, we're done + sass_delete_compiler(compiler); + // exit status + return status; ## Memory handling and life-cycles -We keep memory around for as long as the main [context](api-context.md) object -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`. +The C-API mandates that you have one delete/free call for every make call. Internally +LibSass sometimes utilizes reference counting, but you still need to call the appropriate +`sass_delete` function for every object you own. APIs that return a `char*` also need +the returned memory to be freed by `sass_free_c_string`. A few APIs also allow +implementors to take over ownership of some data. Otherwise this data is attached +to the life-time of the main Compiler object. Although reference counted objects +will stay alive, even after you've called `sass_delete_compiler`. ```C -// to allocate buffer to be filled +// Allocate a memory block on the heap of (at least) [size]. +// Make sure to release to acquired memory at some later point via +// `sass_free_memory`. You need to go through my utility function in +// case your code and my main program don't use the same memory manager. void* sass_alloc_memory(size_t size); -// to allocate a buffer from existing string + +// Allocate a memory block on the heap and copy [string] into it. +// Make sure to release to acquired memory at some later point via +// `sass_free_memory`. You need to go through my utility function in +// case your code and my main program don't use the same memory manager. char* sass_copy_c_string(const char* str); -// to free overtaken memory when done + +// Deallocate libsass heap memory void sass_free_memory(void* ptr); +void sass_free_c_string(char* ptr); ``` ## Miscellaneous API functions ```C -// Some convenient string helper function -char* sass_string_unquote (const char* str); -char* sass_string_quote (const char* str, const char quote_mark); +// Change the virtual current working directory +void sass_chdir(const char* path); -// Get compiled libsass version +// Prints message to stderr with color for windows +void sass_print_stdout(const char* message); +void sass_print_stderr(const char* message); + +// Get compiled LibSass version const char* libsass_version(void); // Implemented sass language version -// Hardcoded version 3.4 for time being +// Hardcoded version 3.9 for time being const char* libsass_language_version(void); ``` -## Common Pitfalls +## Miscellaneous API enums -**input_path** - -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 useful to pass this information if you have a -`Sass_Data_Context` and know the original path. - -**output_path** +```C +// Different render styles +enum SassOutputStyle { + SASS_STYLE_NESTED, + SASS_STYLE_EXPANDED, + SASS_STYLE_COMPACT, + SASS_STYLE_COMPRESSED, + // only used internally! + SASS_STYLE_TO_CSS +}; + +// Type of parser to use +enum SassImportSyntax { + SASS_IMPORT_AUTO, + SASS_IMPORT_SCSS, + SASS_IMPORT_SASS, + SASS_IMPORT_CSS, +}; + +// Config how to produce source-map +enum SassSrcMapMode { + // Don't render any source-mapping. + SASS_SRCMAP_NONE, + // Only render the `srcmap` string. + // The `footer` will be `NULL`. + SASS_SRCMAP_CREATE, + // Write srcmap link into `footer` + SASS_SRCMAP_EMBED_LINK, + // Embed srcmap into `footer` + SASS_SRCMAP_EMBED_JSON, +}; + +// State of the compiler object +enum SassCompilerState { + SASS_COMPILER_CREATED, + SASS_COMPILER_PARSED, + SASS_COMPILER_COMPILED, + SASS_COMPILER_RENDERED, + SASS_COMPILER_DESTROYED +}; +``` -Be aware that `libsass` does not write the output file itself. This option -merely exists to give `libsass` the proper information to generate links in -source-maps. The file has to be written to the disk by the -binding/implementation. If the `output_path` is omitted, `libsass` tries to -extrapolate one from the `input_path` by replacing (or adding) the file ending -with `.css`. +## Common Pitfalls ## Error Codes @@ -199,26 +212,12 @@ have all features implemented! 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) -## ABI forward compatibility - -We use a functional API to make dynamic linking more robust and future -compatible. The API is not yet 100% stable, so we do not yet guarantee -[ABI](https://gcc.gnu.org/onlinedocs/libstdc++/manual/abi.html) forward -compatibility. - ## Plugins (experimental) -LibSass can load plugins from directories. Just define `plugin_path` on context -options to load all plugins from the directories. To implement plugins, please -consult the following example implementations. +LibSass can [load plugins](dev-plugins.md) from directories. Just define `plugin_path` +on context options to load all plugins from the directories. To implement plugins, +please consult the following example implementations. - https://github.com/mgreter/libsass-glob - https://github.com/mgreter/libsass-math - https://github.com/mgreter/libsass-digest - -## Internal Structs - -- [Sass Context Internals](api-context-internal.md) -- [Sass Value Internals](api-value-internal.md) -- [Sass Function Internals](api-function-internal.md) -- [Sass Importer Internals](api-importer-internal.md) diff --git a/docs/api-error.md b/docs/api-error.md new file mode 100644 index 0000000000..c1cbfdd479 --- /dev/null +++ b/docs/api-error.md @@ -0,0 +1,60 @@ +## LibSass C-API for errors + +API to get additional information for errors occurring during custom functions. +Error object are not reference counted and are coupled to the compiler life-cycle. +You can also use them to inspect errors after the compiler failed at any phase. + +### Basic Usage + +```C +#include +``` + +## Sass Function API + +```C +///////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////// + +// Error related getters (use after compiler was rendered) +int sass_error_get_status(const struct SassError* error); + +// Getter for plain error message (use after compiler was rendered). +const char* sass_error_get_string(const struct SassError* error); + +// Getter for error status as css (In order to show error in browser). +// Memory returned by this function must be freed via `sass_free_c_string`. +char* sass_error_get_css(const struct SassError* error); + +// Getter for error status as json object (Useful to pass to downstream). +// Memory returned by this function must be freed via `sass_free_c_string`. +char* sass_error_get_json(const struct SassError* error); + +// Getter for formatted error message. According to logger style this +// may be in unicode and may contain ANSI escape codes for colors. +const char* sass_error_get_formatted(const struct SassError* error); + +// Getter for line position where error occurred (starts from 1). +size_t sass_error_get_line(const struct SassError* error); + +// Getter for column position where error occurred (starts from 1). +size_t sass_error_get_column(const struct SassError* error); + +// Getter for source content referenced in line and column. +const char* sass_error_get_content(const struct SassError* error); + +// Getter for path where the error occurred. +const char* sass_error_get_path(const struct SassError* error); + +// Getter for number of traces attached to error object. +size_t sass_error_count_traces(const struct SassError* error); + +// Getter for last trace (or nullptr if none are available). +const struct SassTrace* sass_error_last_trace(const struct SassError* error); + +// Getter for nth trace (or nullptr if `n` is invalid). +const struct SassTrace* sass_error_get_trace(const struct SassError* error, size_t n); + +///////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////// +``` diff --git a/docs/api-function-example.md b/docs/api-function-example.md index 38608e1a27..f77c1b03fa 100644 --- a/docs/api-function-example.md +++ b/docs/api-function-example.md @@ -1,58 +1,58 @@ ## Example main.c ```C -#include -#include -#include "sass/context.h" +#include +#include +#include +#include -union Sass_Value* call_fn_foo(const union Sass_Value* s_args, Sass_Function_Entry cb, struct Sass_Compiler* comp) +struct Cookie { + double number; +}; + +// Note: some compilers allow to directly use `struct Cookie*` instead of `void*` for the cookie ptr. +struct SassValue* call_fn_foo(struct SassValue* s_args, struct SassCompiler* compiler, void* cookie) { - // get context/option struct associated with this compiler - struct Sass_Context* ctx = sass_compiler_get_context(comp); - struct Sass_Options* opts = sass_compiler_get_options(comp); - // get information about previous importer entry from the stack - Sass_Import_Entry import = sass_compiler_get_last_import(comp); - const char* prev_abs_path = sass_import_get_abs_path(import); - const char* prev_imp_path = sass_import_get_imp_path(import); - // get the cookie from function descriptor - void* cookie = sass_function_get_cookie(cb); - // we actually abuse the void* to store an "int" - return sass_make_number((intptr_t)cookie, "px"); + // Statically cast to type we passed the cookie + struct Cookie* casted = (struct Cookie*)cookie; + // Now we can access whatever we put into the cookie + return sass_make_number(casted->number, "px"); } -int main( int argc, const char* argv[] ) +int main(int argc, const char* argv[]) { - // get the input file from first argument or use default + // Get the input file from first argument or use default const char* input = argc > 1 ? argv[1] : "styles.scss"; - // create the file context and get all related structs - struct Sass_File_Context* file_ctx = sass_make_file_context(input); - struct Sass_Context* ctx = sass_file_context_get_context(file_ctx); - struct Sass_Options* ctx_opt = sass_context_get_options(ctx); + // Create the main compiler object instance + struct SassCompiler* compiler = sass_make_compiler(); + + // Cookie structure to attach to function + // Allows you to pass anything you want + struct Cookie cookie = { 42 }; - // allocate a custom function caller - Sass_Function_Entry fn_foo = - sass_make_function("foo()", call_fn_foo, (void*)42); + // This may fail and produce an error if + // the passed signature cannot be parsed. + sass_compiler_add_custom_function(compiler, + sass_make_function("foo()", call_fn_foo, (void*)&cookie)); - // create list of all custom functions - Sass_Function_List fn_list = sass_make_function_list(1); - sass_function_set_list_entry(fn_list, 0, fn_foo); - sass_option_set_c_functions(ctx_opt, fn_list); + // Create import, set as entry point and release our usage + struct SassImport* import = sass_make_file_import(input); + sass_compiler_set_entry_point(compiler, import); + sass_delete_import(import); - // context is set up, call the compile step now - int status = sass_compile_file_context(file_ctx); + // Execute compiler and print/write results + sass_compiler_execute(compiler, false); - // 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)); + // Get result code after all compilation steps + int status = sass_compiler_get_status(compiler); - // release allocated memory - sass_delete_file_context(file_ctx); + // Clean-up compiler, we're done + sass_delete_compiler(compiler); // exit status return status; - } ``` @@ -60,7 +60,7 @@ int main( int argc, const char* argv[] ) ```bash gcc -c main.c -o main.o -gcc -o sample main.o -lsass +g++ -o sample main.o -lsass echo "foo { margin: foo(); }" > foo.scss ./sample foo.scss => "foo { margin: 42px }" ``` diff --git a/docs/api-function-internal.md b/docs/api-function-internal.md deleted file mode 100644 index 69d81d04d1..0000000000 --- a/docs/api-function-internal.md +++ /dev/null @@ -1,8 +0,0 @@ -```C -// Struct to hold custom function callback -struct Sass_Function { - const char* signature; - Sass_Function_Fn function; - void* cookie; -}; -``` diff --git a/docs/api-function.md b/docs/api-function.md index 8d9d97ca4e..55ab30e41f 100644 --- a/docs/api-function.md +++ b/docs/api-function.md @@ -1,4 +1,11 @@ -Sass functions are used to define new custom functions callable by Sass code. They are also used to overload debug or error statements. You can also define a fallback function, which is called for every unknown function found in the Sass code. Functions get passed zero or more `Sass_Values` (a `Sass_List` value) and they must also return a `Sass_Value`. Return a `Sass_Error` if you want to signal an error. +## LibSass custom functions C-API + +Sass functions are used to define custom functions callable by Sass code. +They are also used to overload `@debug`, `@warn` or `@error` rules. Additionally you +can define a fallback function, which is called for every unknown function found in +your Sass code. Functions get passed zero or more `SassValues` (a `SassList` value) +and they must also return a `SassValue`. Any custom function may also return +a `SassError` value to signal an error during execution. ## Special signatures @@ -7,68 +14,52 @@ Sass functions are used to define new custom functions callable by Sass code. Th - `@error` - Overload error statements - `@debug` - Overload debug statements -Note: The fallback implementation will be given the name of the called function as the first argument, before all the original function arguments. These features are pretty new and should be considered experimental. +Note: The fallback implementation will be given the name of the called function +as the first argument, before all the original function arguments. These features +are pretty new and should be considered experimental. + +### Example code + +See [sass function code example](api-function-example.md). ### Basic Usage ```C -#include "sass/functions.h" +#include ``` ## Sass Function API ```C -// Forward declaration -struct Sass_Compiler; -struct Sass_Function; - -// Typedef helpers for custom functions lists -typedef struct Sass_Function (*Sass_Function_Entry); -typedef struct Sass_Function* (*Sass_Function_List); -// Typedef defining function signature and return type -typedef union Sass_Value* (*Sass_Function_Fn) - (const union Sass_Value*, Sass_Function_Entry cb, struct Sass_Compiler* compiler); - -// Creators for sass function list and function descriptors -Sass_Function_List sass_make_function_list (size_t length); -Sass_Function_Entry sass_make_function (const char* signature, Sass_Function_Fn cb, void* cookie); -// In case you need to free them yourself -void sass_delete_function (Sass_Function_Entry entry); -void sass_delete_function_list (Sass_Function_List list); - -// Setters and getters for callbacks on function lists -Sass_Function_Entry sass_function_get_list_entry(Sass_Function_List list, size_t pos); -void sass_function_set_list_entry(Sass_Function_List list, size_t pos, Sass_Function_Entry cb); - -// Setters to insert an entry into the import list (you may also use [] access directly) -// Since we are dealing with pointers they should have a guaranteed and fixed size -void sass_import_set_list_entry (Sass_Import_List list, size_t idx, Sass_Import_Entry entry); -Sass_Import_Entry sass_import_get_list_entry (Sass_Import_List list, size_t idx); - -// Getters for custom function descriptors -const char* sass_function_get_signature (Sass_Function_Entry cb); -Sass_Function_Fn sass_function_get_function (Sass_Function_Entry cb); -void* sass_function_get_cookie (Sass_Function_Entry cb); - -// Getters for callee entry -const char* sass_callee_get_name (Sass_Callee_Entry); -const char* sass_callee_get_path (Sass_Callee_Entry); -size_t sass_callee_get_line (Sass_Callee_Entry); -size_t sass_callee_get_column (Sass_Callee_Entry); -enum Sass_Callee_Type sass_callee_get_type (Sass_Callee_Entry); -Sass_Env_Frame sass_callee_get_env (Sass_Callee_Entry); - -// Getters and Setters for environments (lexical, local and global) -union Sass_Value* sass_env_get_lexical (Sass_Env_Frame, const char*); -void sass_env_set_lexical (Sass_Env_Frame, const char*, union Sass_Value*); -union Sass_Value* sass_env_get_local (Sass_Env_Frame, const char*); -void sass_env_set_local (Sass_Env_Frame, const char*, union Sass_Value*); -union Sass_Value* sass_env_get_global (Sass_Env_Frame, const char*); -void sass_env_set_global (Sass_Env_Frame, const char*, union Sass_Value*); -``` +///////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////// + +// Type definition for custom functions +typedef struct SassValue* (*SassFunctionLambda)( + struct SassValue*, struct SassCompiler* compiler, void* cookie); + +///////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////// -### More links +// Create custom function (with arbitrary data pointer called `cookie`) +// The pointer is often used to store the callback into the actual binding. +struct SassFunction* sass_make_function (const char* signature, SassFunctionLambda lambda, void* cookie); -- [Sass Function Example](api-function-example.md) -- [Sass Function Internal](api-function-internal.md) +// Deallocate custom function and release memory +void sass_delete_function (struct SassFunction* entry); +///////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////// + +// Getter for custom function signature. +const char* sass_function_get_signature (struct SassFunction* function); + +// Getter for custom function lambda. +SassFunctionLambda sass_function_get_lambda (struct SassFunction* function); + +// Getter for custom function data cookie. +void* sass_function_get_cookie (struct SassFunction* function); + +///////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////// +``` diff --git a/docs/api-import.md b/docs/api-import.md new file mode 100644 index 0000000000..c1fce7cf69 --- /dev/null +++ b/docs/api-import.md @@ -0,0 +1,99 @@ + +## LibSass import C-API + +Imports on the C-API side can either be used as compilation entry points or +imports returned from custom importers/headers. They represent a loadable +resource with text to be parsed. These object are reference-counted and +must always be freed by the allocator. To improve usage with `SassImportList` +there is a convenience method `sass_import_list_emplace` that will transfer +the ownership of the import to the list object. + +### Imports and source-maps + +Source-maps embedded in imports are not automatically parsed out. Ideally we +would want to automatically load associated source-maps for inputs. Currently +LibSass does not make use of any upstream source-maps. The API has been designed +with that case in mind, but implementation is not done yet. Everything still +works, but additional source-maps may end up as superfluous comments. + +### Basic Usage + +```C +#include +``` + +### Sass Import API + +```C +///////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////// + +// Create import entry by reading from `stdin`. +struct SassImport* sass_make_stdin_import(const char* imp_path); + +// Create import entry to load the passed input path. +struct SassImport* sass_make_file_import(const char* imp_path); + +// Create import entry for the passed data with optional path. +// Note: we take ownership of the passed `content` memory. +struct SassImport* sass_make_content_import(char* content, const char* imp_path); + +// Create single import entry returned by the custom importer inside the list. +// Note: source/srcmap can be empty to let LibSass do the file resolving. +// Note: we take ownership of the passed `source` and `srcmap` memory. +struct SassImport* sass_make_import(const char* imp_path, const char* abs_base, + char* source, char* srcmap, enum SassImportSyntax format); + +// Just in case we have some stray import structs +void sass_delete_import(struct SassImport* import); + +///////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////// + +// Getter for specific import format for the given import (force css/sass/scss or set to auto) +enum SassImportSyntax sass_import_get_type(const struct SassImport* import); + +// Setter for specific import format for the given import (force css/sass/scss or set to auto) +void sass_import_set_syntax(struct SassImport* import, enum SassImportSyntax syntax); + +// Getter for original import path (as seen when parsed) +const char* sass_import_get_imp_path(const struct SassImport* import); + +// Getter for resolve absolute path (after being resolved) +const char* sass_import_get_abs_path(const struct SassImport* import); + +// Getter for import error message (used by custom importers). +// If error is not `nullptr`, the import must be considered as failed. +const char* sass_import_get_error_message(struct SassImport* import); + +// Setter for import error message (used by custom importers). +// If error is not `nullptr`, the import must be considered as failed. +void sass_import_set_error_message(struct SassImport* import, const char* msg); + +///////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////// + +// Create new list container for imports. +struct SassImportList* sass_make_import_list(); + +// Release memory of list container and all children. +void sass_delete_import_list(struct SassImportList* list); + +///////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////// + +// Return number of items currently in the list. +size_t sass_import_list_size(struct SassImportList* list); + +// Remove and return first item in the list (as in a fifo queue). +struct SassImport* sass_import_list_shift(struct SassImportList* list); + +// Append an additional import to the list container. +void sass_import_list_push(struct SassImportList* list, struct SassImport* import); + +// Append an additional import to the list container and takes ownership of the import. +void sass_import_list_emplace(struct SassImportList* list, struct SassImport* import); + +///////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////// +``` diff --git a/docs/api-importer-example.md b/docs/api-importer-example.md index d83bf26098..bd96d43328 100644 --- a/docs/api-importer-example.md +++ b/docs/api-importer-example.md @@ -3,50 +3,41 @@ ```C #include #include -#include "sass/context.h" +#include "sass/compiler.h" +#include "sass/importer.h" -Sass_Import_List sass_importer(const char* path, Sass_Importer_Entry cb, struct Sass_Compiler* comp) +struct SassImportList* sass_importer(SassImporterLambda lambda, double priority, void* cookie) { // get the cookie from importer descriptor - void* cookie = sass_importer_get_cookie(cb); - Sass_Import_List list = sass_make_import_list(2); + struct SassImportList* list = sass_make_import_list(); char* local = sass_copy_c_string("local { color: green; }"); char* remote = sass_copy_c_string("remote { color: red; }"); - list[0] = sass_make_import_entry("/tmp/styles.scss", local, 0); - list[1] = sass_make_import_entry("http://www.example.com", remote, 0); + sass_import_list_emplace(list, sass_make_content_import(local, "/tmp/styles.scss")); + sass_import_list_emplace(list, sass_make_content_import(remote, "http://www.example.com")); return list; } -int main( int argc, const char* argv[] ) +int main(int argc, const char* argv[]) { - - // get the input file from first argument or use default - const char* input = argc > 1 ? argv[1] : "styles.scss"; - + // create compiler object holding config and states + struct SassCompiler* compiler = sass_make_compiler(); // create the file context and get all related structs - struct Sass_File_Context* file_ctx = sass_make_file_context(input); - struct Sass_Context* ctx = sass_file_context_get_context(file_ctx); - struct Sass_Options* ctx_opt = sass_context_get_options(ctx); + struct SassImport* import = sass_make_content_import("@import 'foobar';", 0); + // each compiler must have exactly one entry point + sass_compiler_set_entry_point(compiler, import); + // entry point now passed to compiler, so its reference count was increased + // in order to not leak memory we must release our own usage (usage after is UB) + sass_delete_import(import); // decrease ref-count // allocate custom importer - Sass_Importer_Entry c_imp = + struct SassImporter* importer = sass_make_importer(sass_importer, 0, 0); // create list for all custom importers - Sass_Importer_List imp_list = sass_make_importer_list(1); - // put only the importer on to the list - sass_importer_set_list_entry(imp_list, 0, c_imp); - // register list on to the context options - sass_option_set_c_importers(ctx_opt, imp_list); + sass_compiler_add_custom_importer(compiler, importer); // context is set up, call the compile step now - int status = sass_compile_file_context(file_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)); - + int status = sass_compiler_execute(compiler, false); // release allocated memory - sass_delete_file_context(file_ctx); - + sass_delete_compiler(compiler); // exit status return status; @@ -56,8 +47,8 @@ int main( int argc, const char* argv[] ) Compile importer.c ```bash -gcc -c importer.c -o importer.o -gcc -o importer importer.o -lsass +gcc -c importer.c -o importer.o -Iinclude +g++ -o importer importer.o -lsass -Llib echo "@import 'foobar';" > importer.scss ./importer importer.scss ``` @@ -65,48 +56,41 @@ echo "@import 'foobar';" > importer.scss ## Importer Behavior Examples ```C -Sass_Import_List importer(const char* path, Sass_Importer_Entry cb, struct Sass_Compiler* comp) { - // let LibSass handle the import request +struct SassImportList* importer(SassImporterLambda lambda, double priority, void* cookie) { + // Skip this importer and try next in queue return NULL; } -Sass_Import_List importer(const char* path, Sass_Importer_Entry cb, struct Sass_Compiler* comp) { - // let LibSass handle the request - // swallows »@import "http://…"« pass-through - // (arguably a bug) - Sass_Import_List list = sass_make_import_list(1); - list[0] = sass_make_import_entry(path, 0, 0); +struct SassImportList* importer(SassImporterLambda lambda, double priority, void* cookie) { + // Let LibSass load the file identified by the importer + // No further importers are consulted + struct SassImportList* list = sass_make_import_list(); + sass_import_list_emplace(list, sass_make_file_import(path)); return list; } -Sass_Import_List importer(const char* path, Sass_Importer_Entry cb, struct Sass_Compiler* comp) { - // return an error to halt execution - Sass_Import_List list = sass_make_import_list(1); +struct SassImportList* importer(SassImporterLambda lambda, double priority, void* cookie) { + // Return an error to halt execution + struct SassImportList* list = sass_make_import_list(); const char* message = "some error message"; - list[0] = sass_make_import_entry(path, 0, 0); - sass_import_set_error(list[0], sass_copy_c_string(message), 0, 0); + struct SassImport* import = sass_make_file_import(path); + sass_import_set_error_message(import, sass_copy_c_string(message)); + sass_import_list_emplace(list, import); return list; } -Sass_Import_List importer(const char* path, Sass_Importer_Entry cb, struct Sass_Compiler* comp) { - // let LibSass load the file identifed by the importer - Sass_Import_List list = sass_make_import_list(1); - list[0] = sass_make_import_entry("/tmp/file.scss", 0, 0); +struct SassImportList* importer(SassImporterLambda lambda, double priority, void* cookie) { + // Let LibSass load the file identified by the importer + struct SassImportList* list = sass_make_import_list(); + sass_import_list_emplace(list, sass_make_file_import(path)); return list; } -Sass_Import_List importer(const char* path, Sass_Importer_Entry cb, struct Sass_Compiler* comp) { - // completely hide the import - // (arguably a bug) - Sass_Import_List list = sass_make_import_list(0); +struct SassImportList* importer(SassImporterLambda lambda, double priority, void* cookie) { + // Completely hide the import + // No further importers are consulted + struct SassImportList* list = sass_make_import_list(); return list; } -Sass_Import_List importer(const char* path, Sass_Importer_Entry cb, struct Sass_Compiler* comp) { - // completely hide the import - // (arguably a bug) - Sass_Import_List list = sass_make_import_list(1); - list[0] = sass_make_import_entry(0, 0, 0); - return list; -} ``` diff --git a/docs/api-importer-internal.md b/docs/api-importer-internal.md deleted file mode 100644 index 63d70fe757..0000000000 --- a/docs/api-importer-internal.md +++ /dev/null @@ -1,20 +0,0 @@ -```C -// External import entry -struct Sass_Import { - char* imp_path; // path as found in the import statement - char *abs_path; // path after importer has resolved it - char* source; - char* srcmap; - // error handling - char* error; - size_t line; - size_t column; -}; - -// Struct to hold importer callback -struct Sass_Importer { - Sass_Importer_Fn importer; - double priority; - void* cookie; -}; -``` diff --git a/docs/api-importer.md b/docs/api-importer.md index b6265002ee..adb4968683 100644 --- a/docs/api-importer.md +++ b/docs/api-importer.md @@ -1,86 +1,98 @@ -By using custom importers, Sass stylesheets can be implemented in any possible way, such as by being loaded via a remote server. Please note: this feature is experimental and is implemented differently than importers in Ruby Sass. Imports must be relative to the parent import context and therefore we need to pass this information to the importer callback. This is currently done by passing the complete import string/path of the previous import context. - -## Return Imports - -You actually have to return a list of imports, since some importers may want to import multiple files from one import statement (ie. a glob/star importer). The memory you pass with source and srcmap is taken over by LibSass and freed automatically when the import is done. You are also allowed to return `0` instead of a list, which will tell LibSass to handle the import by itself (as if no custom importer was in use). +## LibSass custom importer C-API + +Note: currently custom importers are not yet implemented for `@use` and friends! + +The custom importer C-API allows implementors to decide how `@import` and similar +rules (e.g. `@use`) are handled. Custom importers will we called once we try to +resolve an import rule. Without any custom importer, LibSass will try to resolve +the import by looking into all include directories. Custom importers will be invoked +before this happens, if any are registered. LibSass passes the current path that +should be imported and the resolved path to the parent import, in order for the +custom importer to resolve relative imports to the parent style-sheet. + +The custom importer must return a pointer to a `SassImportList`, with which it can +represent three different states. It can return a `nullptr`, which will let LibSass +execute the next custom importer. It can return an empty `SassImportList`, which will +mean that the import is consumed, but nothing is really imported. Last it can return +one or more entries in the `SassImportList`. That list consists of `SassImport` entries, +which can either be just rewritten paths, or also already have fully loaded content. + +### C-API for `SassImportList` + +You have to return a list of imports, since some importers may want to import multiple +files from one import statement (ie. a glob/star importer). The memory you pass with +source and srcmap is taken over by LibSass and freed automatically when the import is +done. You are also allowed to return `0` or `nullptr` instead of a list, which will +tell LibSass to handle the import by itself (as if no custom importer was in use). +The C-API for `SassImportList` is designed like a FiFo queue (first in, first out). +The C-API only allows to push or shift items from the list. ```C -Sass_Import_Entry* rv = sass_make_import_list(1); -rv[0] = sass_make_import(rel, abs, source, srcmap); +struct SassImportList* imports = sass_make_import_list(); +sass_import_list_push(imports, sass_import_list_emplace(rel, abs, source, srcmap)); +struct SassImport* import = sass_import_list_shift(imports); ``` -Every import will then be included in LibSass. You are allowed to only return a file path without any loaded source. This way you can ie. implement rewrite rules for import paths and leave the loading part for LibSass. +Every import will then be included in LibSass. You are allowed to only return a file path +without any loaded source. This way you can ie. implement rewrite rules for import paths +and leave the loading part for LibSass. + +Please note that LibSass doesn't use the srcmap parameter yet. It has been added to not +deprecate the C-API once support has been implemented. It will be used to re-map the +actual sourcemap with the provided ones. + +### Difference to official Sass implementation -Please note that LibSass doesn't use the srcmap parameter yet. It has been added to not deprecate the C-API once support has been implemented. It will be used to re-map the actual sourcemap with the provided ones. +According to the official dart-sass implementation, we should not call custom importers for +e.g. css imports, but LibSass will always try to query registered custom importers for every +import it encounters. In order for custom importers to stay 100% within the Sass specifications, +it should ignore any imports whose URLs end in .css or begin with http:// or https://, or imports +that have media queries, should always be treated as plain CSS and never passed to custom importers. +For further details check https://github.com/sass/libsass/issues/2957 and linked issues. + +### Example code + +See [sass importer code example](api-importer-example.md). ### Basic Usage ```C -#include "sass/functions.h" +#include ``` ## Sass Importer API ```C -// Forward declaration -struct Sass_Import; - -// Forward declaration -struct Sass_C_Import_Descriptor; - -// Typedef defining the custom importer callback -typedef struct Sass_C_Import_Descriptor (*Sass_C_Import_Callback); -// Typedef defining the importer c function prototype -typedef Sass_Import_Entry* (*Sass_C_Import_Fn) (const char* url, const char* prev, void* cookie); - -// Creators for custom importer callback (with some additional pointer) -// The pointer is mostly used to store the callback into the actual function -Sass_C_Import_Callback sass_make_importer (Sass_C_Import_Fn, void* cookie); - -// Getters for import function descriptors -Sass_C_Import_Fn sass_import_get_function (Sass_C_Import_Callback fn); -void* sass_import_get_cookie (Sass_C_Import_Callback fn); - -// Deallocator for associated memory -void sass_delete_importer (Sass_C_Import_Callback fn); - -// Creator for sass custom importer return argument list -Sass_Import_Entry* sass_make_import_list (size_t length); -// Creator for a single import entry returned by the custom importer inside the list -Sass_Import_Entry sass_make_import_entry (const char* path, char* source, char* srcmap); -Sass_Import_Entry sass_make_import (const char* rel, const char* abs, char* source, char* srcmap); - -// set error message to abort import and to print out a message (path from existing object is used in output) -Sass_Import_Entry sass_import_set_error(Sass_Import_Entry import, const char* message, size_t line, size_t col); - -// Setters to insert an entry into the import list (you may also use [] access directly) -// Since we are dealing with pointers they should have a guaranteed and fixed size -void sass_import_set_list_entry (Sass_Import_Entry* list, size_t idx, Sass_Import_Entry entry); -Sass_Import_Entry sass_import_get_list_entry (Sass_Import_Entry* list, size_t idx); - -// Getters for import entry -const char* sass_import_get_imp_path (Sass_Import_Entry); -const char* sass_import_get_abs_path (Sass_Import_Entry); -const char* sass_import_get_source (Sass_Import_Entry); -const char* sass_import_get_srcmap (Sass_Import_Entry); -// Explicit functions to take ownership of these items -// The property on our struct will be reset to NULL -char* sass_import_take_source (Sass_Import_Entry); -char* sass_import_take_srcmap (Sass_Import_Entry); - -// Getters for import error entries -size_t sass_import_get_error_line (Sass_Import_Entry); -size_t sass_import_get_error_column (Sass_Import_Entry); -const char* sass_import_get_error_message (Sass_Import_Entry); - -// Deallocator for associated memory (incl. entries) -void sass_delete_import_list (Sass_Import_Entry*); -// Just in case we have some stray import structs -void sass_delete_import (Sass_Import_Entry); -``` +///////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////// -### More links +// Type definitions for importer functions +typedef struct SassImportList* (*SassImporterLambda)( + const char* url, struct SassImporter* cb, struct SassCompiler* compiler); -- [Sass Importer Example](api-importer-example.md) -- [Sass Importer Internal](api-importer-internal.md) +///////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////// +// Create custom importer (with arbitrary data pointer called `cookie`) +// The pointer is often used to store the callback into the actual binding. +struct SassImporter* sass_make_importer( + SassImporterLambda lambda, double priority, void* cookie); + +// Deallocate the importer and release memory +void sass_delete_importer(struct SassImporter* cb); + +///////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////// + +// Getter for importer lambda function (the one being actually invoked) +SassImporterLambda sass_importer_get_lambda(struct SassImporter* cb); + +// Getter for importer priority (lowest priority is invoked first) +double sass_importer_get_priority(struct SassImporter* cb); + +// Getter for arbitrary cookie (used by implementers to store stuff) +void* sass_importer_get_cookie(struct SassImporter* cb); + +///////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////// +``` diff --git a/docs/api-traces.md b/docs/api-traces.md new file mode 100644 index 0000000000..4d460c3072 --- /dev/null +++ b/docs/api-traces.md @@ -0,0 +1,78 @@ +## LibSass C-API for stack traces + +API for fetching additional information about the current import/call stack. +This is useful for custom importers, custom functions or in case of errors. + +### Basic Usage + +```C +#include +``` + +### Sass stack trace related APIs + +```C +///////////////////////////////////////////////////////////////////////// +// Implementation related to struct SassTrace +///////////////////////////////////////////////////////////////////////// + +// Getter for name of this trace (normally the function name or empty). +const char* sass_trace_get_name(struct SassTrace* trace); + +// Getter to check if trace is from a function call (otherwise import). +bool sass_trace_was_fncall(struct SassTrace* trace); + +// Getter for the SourceSpan (aka ParserState) for further details +const struct SassSrcSpan* sass_trace_get_srcspan(struct SassTrace* trace); + +///////////////////////////////////////////////////////////////////////// +// Implementation related to struct SassSrcSpan +///////////////////////////////////////////////////////////////////////// + +// Getter for line position of trace (starting from 0) +size_t sass_srcspan_get_src_ln(struct SassSrcSpan* pstate); + +// Getter for column position of trace (starting from 0) +size_t sass_srcspan_get_src_col(struct SassSrcSpan* pstate); + +// Getter for line position of trace (starting from 1) +size_t sass_srcspan_get_src_line(struct SassSrcSpan* pstate); + +// Getter for column position of trace (starting from 1) +size_t sass_srcspan_get_src_column(struct SassSrcSpan* pstate); + +// Getter for line span of trace (starting from 0) +size_t sass_srcspan_get_span_ln(struct SassSrcSpan* pstate); + +// Getter for column span of trace (starting from 0) +size_t sass_srcspan_get_span_col(struct SassSrcSpan* pstate); + +// Getter for attached source of trace for further details +struct SassSource* sass_srcspan_get_source(struct SassSrcSpan* pstate); + +///////////////////////////////////////////////////////////////////////// +// Implementation related to struct SassSource +///////////////////////////////////////////////////////////////////////// + +// Getter for absolute path this source was loaded from. This path should +// always be absolute but there is no real hard requirement for it. Custom +// importers may use different pattern for paths. LibSass tries to support +// regular win/nix paths and urls. But we it also try to be agnostic here, +// so anything a custom importer returns will be returned here. +const char* sass_source_get_abs_path(struct SassSource* source); + +// Getter for import path this source was loaded from. This path should +// be as it was found when the import was parsed. This is merely useful +// for debugging purposes, but we keep it around anyway. +const char* sass_source_get_imp_path(struct SassSource* source); + +// Getter for the loaded content attached to the source. +const char* sass_source_get_content(struct SassSource* source); + +// Getter for the loaded srcmap attached to the source. +// Note: not used yet, only here for future improvements. +const char* sass_source_get_srcmap(struct SassSource* source); + +///////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////// +``` diff --git a/docs/api-value-example.md b/docs/api-value-example.md index 2d332110aa..e5414f3168 100644 --- a/docs/api-value-example.md +++ b/docs/api-value-example.md @@ -3,17 +3,17 @@ ```C #include #include -#include "sass/values.h" +#include int main( int argc, const char* argv[] ) { // create two new sass values to be added - union Sass_Value* string = sass_make_string("String"); - union Sass_Value* number = sass_make_number(42, "nits"); + struct SassValue* string = sass_make_string("prefix", false); + struct SassValue* number = sass_make_number(42, "nits"); // invoke the add operation which returns a new sass value - union Sass_Value* total = sass_value_op(ADD, string, number); + struct SassValue* total = sass_value_op(ADD, string, number); // no further use for the two operands sass_delete_value(string); @@ -25,7 +25,7 @@ int main( int argc, const char* argv[] ) puts(sass_string_get_value(total)); // invoke stringification (uncompressed with precision of 5) - union Sass_Value* result = sass_value_stringify(total, false, 5); + struct SassValue* result = sass_value_stringify(total, false, 5); // no further use for the sum sass_delete_value(total); @@ -49,7 +49,7 @@ int main( int argc, const char* argv[] ) ## Compile operation.c ```bash -gcc -c operation.c -o operation.o -gcc -o operation operation.o -lsass -./operation # => String42nits +gcc -c operation.c -o operation.o -Iinclude +g++ -o operation operation.o -lsass -Llib +./operation # => prefix42nits ``` diff --git a/docs/api-value-internal.md b/docs/api-value-internal.md deleted file mode 100644 index fed4022560..0000000000 --- a/docs/api-value-internal.md +++ /dev/null @@ -1,76 +0,0 @@ -```C -struct Sass_Unknown { - enum Sass_Tag tag; -}; - -struct Sass_Boolean { - enum Sass_Tag tag; - bool value; -}; - -struct Sass_Number { - enum Sass_Tag tag; - double value; - char* unit; -}; - -struct Sass_Color { - enum Sass_Tag tag; - double r; - double g; - double b; - double a; -}; - -struct Sass_String { - enum Sass_Tag tag; - char* value; -}; - -struct Sass_List { - enum Sass_Tag tag; - enum Sass_Separator separator; - size_t length; - // null terminated "array" - union Sass_Value** values; -}; - -struct Sass_Map { - enum Sass_Tag tag; - size_t length; - struct Sass_MapPair* pairs; -}; - -struct Sass_Null { - enum Sass_Tag tag; -}; - -struct Sass_Error { - enum Sass_Tag tag; - char* message; -}; - -struct Sass_Warning { - enum Sass_Tag tag; - char* message; -}; - -union Sass_Value { - struct Sass_Unknown unknown; - struct Sass_Boolean boolean; - struct Sass_Number number; - struct Sass_Color color; - struct Sass_String string; - struct Sass_List list; - struct Sass_Map map; - struct Sass_Null null; - struct Sass_Error error; - struct Sass_Warning warning; -}; - -struct Sass_MapPair { - union Sass_Value* key; - union Sass_Value* value; -}; -``` - diff --git a/docs/api-value.md b/docs/api-value.md index d78625875f..aaac22a0e6 100644 --- a/docs/api-value.md +++ b/docs/api-value.md @@ -1,21 +1,67 @@ -`Sass_Values` are used to pass values and their types between the implementer -and LibSass. Sass knows various different value types (including nested arrays +## LibSass C-API for sass values + +`SassValue` is the base type to exchange sass values between implementors and +LibSass. Sass knows various different value types (including nested arrays and hash-maps). If you implement a binding to another programming language, you -have to find a way to [marshal][1] (convert) `Sass_Values` between the target -language and C. `Sass_Values` are currently only used by custom functions, but -it should also be possible to use them without a compiler context. +have to find a way to [marshal][1] (convert) a `SassValue` between the target +language and C. `SassValue` is currently only used by custom functions, but +it should also be able to use them without any explicit compiler. [1]: https://en.wikipedia.org/wiki/Marshalling_%28computer_science%29 +### Handling of containers + +There are two `SassValue` types (list and map) that act as containers for +nested `SassValue` objects. They have different implementations to iterate +over the existing values. The `SassList` acts like any regular array, so you +can get the size and access every item by offset. The `SassMap` has a specific +iterator you need to allocate first in order to iterate over it via `next`. +You also have to make sure to release the memory associated with the iterator. + +#### Iterating over `SassList` + +```C +for (int i = 0; i < sass_list_get_size(list); i += 1) { + struct SassValue* child = sass_list_at(list, i); +} +``` + +#### Iterating over `SassMap` + +```C +struct SassMapIterator* it sass_map_make_iterator(map); +while (!sass_map_iterator_exhausted(it)) { + struct SassValue* key = sass_map_iterator_get_key(it); + struct SassValue* val = sass_map_iterator_get_value(it); + sass_map_iterator_next(it); +} +sass_map_delete_iterator(it); +``` + +### Errors and warnings + +Custom functions may fail for any reason and in order to communicate this state +back to LibSass, any custom function can return a `SassError`, which is a special +type of `SassValue`, solely existing for this purpose. If a custom function returns +this special type, it will throw an error further down. + +Note: `SassWarning` is currently handled in the same way, but warning should ultimately +be a C-API function on its own, as we might want to emit multiple warnings, but still +return a successful return state (we only can have one error, but many warnings). + +### Example code + +See [sass value code example](api-value-example.md). + ### Basic Usage ```C -#include "sass/values.h" +#include ``` ```C -// Type for Sass values -enum Sass_Tag { +// Type of Sass values +enum SassValueType { SASS_BOOLEAN, SASS_NUMBER, SASS_COLOR, @@ -24,131 +70,137 @@ enum Sass_Tag { SASS_MAP, SASS_NULL, SASS_ERROR, - SASS_WARNING + SASS_WARNING, + SASS_FUNCTION }; -// Tags for denoting Sass list separators -enum Sass_Separator { +// List separators +enum SassSeparator { SASS_COMMA, SASS_SPACE, - // only used internally to represent a hash map before evaluation - // otherwise we would be too early to check for duplicate keys - SASS_HASH + // A separator that hasn't yet been determined. + // Singleton lists and empty lists don't have separators defined. This means + // that list functions will prefer other lists' separators if possible. + SASS_UNDEF, }; // Value Operators -enum Sass_OP { - AND, OR, // logical connectives +enum SassOperator { + OR, AND, // logical connectives EQ, NEQ, GT, GTE, LT, LTE, // arithmetic relations ADD, SUB, MUL, DIV, MOD, // arithmetic functions - NUM_OPS // so we know how big to make the op table + IESEQ // special IE single equal }; ``` ### Sass Value API ```C -// Forward declaration -union Sass_Value; - // Creator functions for all value types -union Sass_Value* sass_make_null (void); -union Sass_Value* sass_make_boolean (bool val); -union Sass_Value* sass_make_string (const char* val); -union Sass_Value* sass_make_qstring (const char* val); -union Sass_Value* sass_make_number (double val, const char* unit); -union Sass_Value* sass_make_color (double r, double g, double b, double a); -union Sass_Value* sass_make_list (size_t len, enum Sass_Separator sep, bool is_bracketed); -union Sass_Value* sass_make_map (size_t len); -union Sass_Value* sass_make_error (const char* msg); -union Sass_Value* sass_make_warning (const char* msg); +struct SassValue* sass_make_null(void); +struct SassValue* sass_make_boolean(bool val); +struct SassValue* sass_make_string(const char* val, bool is_quoted); +struct SassValue* sass_make_number(double val, const char* unit); +struct SassValue* sass_make_color(double r, double g, double b, double a); +struct SassValue* sass_make_list(enum SassSeparator sep, bool is_bracketed); +struct SassValue* sass_make_map(void); +struct SassValue* sass_make_error(const char* msg); +struct SassValue* sass_make_warning(const char* msg); // Generic destructor function for all types -// Will release memory of all associated Sass_Values +// Will release memory of all associated SassValue children // Means we will delete recursively for lists and maps -void sass_delete_value (union Sass_Value* val); +void sass_delete_value(struct SassValue* val); // Make a deep cloned copy of the given sass value -union Sass_Value* sass_clone_value (const union Sass_Value* val); - -// Stringify a Sass_Values and also return the result as a Sass_Value (of type STRING) -union Sass_Value* sass_value_stringify (const union Sass_Value* a, bool compressed, int precision); +struct SassValue* sass_clone_value(struct SassValue* val); // Execute an operation for two Sass_Values and return the result as a Sass_Value too -union Sass_Value* sass_value_op (enum Sass_OP op, const union Sass_Value* a, const union Sass_Value* b); +struct SassValue* sass_value_op(enum SassOperator op, struct SassValue* a, struct SassValue* b); + +// Stringify a Sass_Values and also return the result as a Sass_Value (of type STRING) +struct SassValue* sass_value_stringify(struct SassValue* a, bool compressed, int precision); // Return the sass tag for a generic sass value // Check is needed before accessing specific values! -enum Sass_Tag sass_value_get_tag (const union Sass_Value* v); +enum SassValueType sass_value_get_tag(struct SassValue* v); // Check value to be of a specific type // Can also be used before accessing properties! -bool sass_value_is_null (const union Sass_Value* v); -bool sass_value_is_number (const union Sass_Value* v); -bool sass_value_is_string (const union Sass_Value* v); -bool sass_value_is_boolean (const union Sass_Value* v); -bool sass_value_is_color (const union Sass_Value* v); -bool sass_value_is_list (const union Sass_Value* v); -bool sass_value_is_map (const union Sass_Value* v); -bool sass_value_is_error (const union Sass_Value* v); -bool sass_value_is_warning (const union Sass_Value* v); +bool sass_value_is_null(struct SassValue* v); +bool sass_value_is_number(struct SassValue* v); +bool sass_value_is_string(struct SassValue* v); +bool sass_value_is_boolean(struct SassValue* v); +bool sass_value_is_color(struct SassValue* v); +bool sass_value_is_list(struct SassValue* v); +bool sass_value_is_map(struct SassValue* v); +bool sass_value_is_error(struct SassValue* v); +bool sass_value_is_warning(struct SassValue* v); // Getters and setters for Sass_Number -double sass_number_get_value (const union Sass_Value* v); -void sass_number_set_value (union Sass_Value* v, double value); -const char* sass_number_get_unit (const union Sass_Value* v); -void sass_number_set_unit (union Sass_Value* v, char* unit); +double sass_number_get_value(struct SassValue* v); +void sass_number_set_value(struct SassValue* v, double value); +const char* sass_number_get_unit(struct SassValue* v); +void sass_number_set_unit(struct SassValue* v, const char* unit); +void sass_number_normalize(struct SassValue* v); // What does it do? +void sass_number_reduce(struct SassValue* v); // Getters and setters for Sass_String -const char* sass_string_get_value (const union Sass_Value* v); -void sass_string_set_value (union Sass_Value* v, char* value); -bool sass_string_is_quoted(const union Sass_Value* v); -void sass_string_set_quoted(union Sass_Value* v, bool quoted); +const char* sass_string_get_value(struct SassValue* v); +void sass_string_set_value(struct SassValue* v, char* value); +bool sass_string_is_quoted(struct SassValue* v); +void sass_string_set_quoted(struct SassValue* v, bool quoted); // Getters and setters for Sass_Boolean -bool sass_boolean_get_value (const union Sass_Value* v); -void sass_boolean_set_value (union Sass_Value* v, bool value); +bool sass_boolean_get_value(struct SassValue* v); +void sass_boolean_set_value(struct SassValue* v, bool value); // Getters and setters for Sass_Color -double sass_color_get_r (const union Sass_Value* v); -void sass_color_set_r (union Sass_Value* v, double r); -double sass_color_get_g (const union Sass_Value* v); -void sass_color_set_g (union Sass_Value* v, double g); -double sass_color_get_b (const union Sass_Value* v); -void sass_color_set_b (union Sass_Value* v, double b); -double sass_color_get_a (const union Sass_Value* v); -void sass_color_set_a (union Sass_Value* v, double a); - -// Getter for the number of items in list -size_t sass_list_get_length (const union Sass_Value* v); +double sass_color_get_r(struct SassValue* v); +void sass_color_set_r(struct SassValue* v, double r); +double sass_color_get_g(struct SassValue* v); +void sass_color_set_g(struct SassValue* v, double g); +double sass_color_get_b(struct SassValue* v); +void sass_color_set_b(struct SassValue* v, double b); +double sass_color_get_a(struct SassValue* v); +void sass_color_set_a(struct SassValue* v, double a); + +size_t sass_list_get_size(struct SassValue* list); +void sass_list_push(struct SassValue* list, struct SassValue* value); +struct SassValue* sass_list_at(struct SassValue* list, size_t i); +struct SassValue* sass_list_pop(struct SassValue* list, struct SassValue* value); +struct SassValue* sass_list_shift(struct SassValue* list, struct SassValue* value); + // Getters and setters for Sass_List -enum Sass_Separator sass_list_get_separator (const union Sass_Value* v); -void sass_list_set_separator (union Sass_Value* v, enum Sass_Separator value); -bool sass_list_get_is_bracketed (const union Sass_Value* v); -void sass_list_set_is_bracketed (union Sass_Value* v, bool value); +enum SassSeparator sass_list_get_separator(struct SassValue* v); +void sass_list_set_separator(struct SassValue* v, enum SassSeparator separator); +bool sass_list_get_is_bracketed(struct SassValue* v); +void sass_list_set_is_bracketed(struct SassValue* v, bool value); // Getters and setters for Sass_List values -union Sass_Value* sass_list_get_value (const union Sass_Value* v, size_t i); -void sass_list_set_value (union Sass_Value* v, size_t i, union Sass_Value* value); +struct SassValue* sass_list_get_value(struct SassValue* v, size_t i); +void sass_list_set_value(struct SassValue* v, size_t i, struct SassValue* value); + +void sass_map_set(struct SassValue* m, struct SassValue* k, struct SassValue* v); +struct SassMapIterator* sass_map_make_iterator(struct SassValue* map); +void sass_map_delete_iterator(struct SassMapIterator* it); +bool sass_map_iterator_exhausted(struct SassMapIterator* it); +struct SassValue* sass_map_iterator_get_key(struct SassMapIterator* it); +struct SassValue* sass_map_iterator_get_value(struct SassMapIterator* it); +void sass_map_iterator_next(struct SassMapIterator* it); -// Getter for the number of items in map -size_t sass_map_get_length (const union Sass_Value* v); -// Getters and setters for Sass_Map keys and values -union Sass_Value* sass_map_get_key (const union Sass_Value* v, size_t i); -void sass_map_set_key (union Sass_Value* v, size_t i, union Sass_Value*); -union Sass_Value* sass_map_get_value (const union Sass_Value* v, size_t i); -void sass_map_set_value (union Sass_Value* v, size_t i, union Sass_Value*); +void sass_map_set(struct SassValue* m, struct SassValue* k, struct SassValue* v); +struct SassValue* sass_map_get(struct SassValue* m, struct SassValue* k); // Getters and setters for Sass_Error -char* sass_error_get_message (const union Sass_Value* v); -void sass_error_set_message (union Sass_Value* v, char* msg); +const char* sass_error_get_message(struct SassValue* v); +void sass_error_set_message(struct SassValue* v, const char* msg); // Getters and setters for Sass_Warning -char* sass_warning_get_message (const union Sass_Value* v); -void sass_warning_set_message (union Sass_Value* v, char* msg); +const char* sass_warning_get_message(struct SassValue* v); +void sass_warning_set_message(struct SassValue* v, const char* msg); ``` ### More links - [Sass Value Example](api-value-example.md) -- [Sass Value Internal](api-value-internal.md) diff --git a/docs/api-variable.md b/docs/api-variable.md new file mode 100644 index 0000000000..7a81c6a8a0 --- /dev/null +++ b/docs/api-variable.md @@ -0,0 +1,40 @@ +## LibSass C-API for variables + +This API allows custom functions to access and modify existing variables. +It is not possible to create new variables with this API, as variables are +optimized in a way that declarations are hard baked once parsing is done. +Therefore we can't add new variables during runtime. This API allows to +query and set already existing variables. To define new variables you must +use an API that is executed during the parsing phase (see plugins). + +### Basic Usage + +```C +#include +``` + +### Sass run-time variable API + +```C +///////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////// + +// Getter for lexical variable (lexical to scope where function is called). +// Note: C-API function can only access existing variables and not create new ones! +struct SassValue* sass_env_get_lexical (struct SassCompiler* compiler, const char* name); + +// Setter for lexical variable (lexical to scope where function is called). +// Note: C-API function can only access existing variables and not create new ones! +void sass_env_set_lexical(struct SassCompiler* compiler, const char* name, struct SassValue* value); + +// Getter for local variable (local only to scope where function is called). +// Note: C-API function can only access existing variables and not create new ones! +struct SassValue* sass_env_get_global (struct SassCompiler* compiler, const char* name); + +// Setter for local variable (local only to scope where function is called). +// Note: C-API function can only access existing variables and not create new ones! +void sass_env_set_global(struct SassCompiler* compiler, const char* name, struct SassValue* value); + +///////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////// +``` diff --git a/docs/build-on-gentoo.md b/docs/build-on-gentoo.md deleted file mode 100644 index 1e71492d1a..0000000000 --- a/docs/build-on-gentoo.md +++ /dev/null @@ -1,55 +0,0 @@ -Here are two ebuilds to compile LibSass and sassc on gentoo linux. If you do not know how to use these ebuilds, you should probably read the gentoo wiki page about [portage overlays](http://wiki.gentoo.org/wiki/Overlay). - -## www-misc/libsass/libsass-9999.ebuild -```ebuild -EAPI=4 - -inherit eutils git-2 autotools - -DESCRIPTION="A C/C++ implementation of a Sass compiler." -HOMEPAGE="https://sass-lang.com/" -EGIT_PROJECT='libsass' -EGIT_REPO_URI="https://github.com/sass/libsass.git" -LICENSE="MIT" -SLOT="0" -KEYWORDS="" -IUSE="" -DEPEND="" -RDEPEND="${DEPEND}" -DEPEND="${DEPEND}" - -pkg_pretend() { - # older gcc is not supported - local major=$(gcc-major-version) - local minor=$(gcc-minor-version) - [[ "${MERGE_TYPE}" != "binary" && ( $major > 4 || ( $major == 4 && $minor < 5 ) ) ]] && \ - die "Sorry, but gcc earlier than 4.5 will not work for LibSass." -} - -src_prepare() { - eautoreconf -} -``` - -## www-misc/sassc/sassc-9999.ebuild -```ebuild -EAPI=4 - -inherit eutils git-2 autotools - -DESCRIPTION="Command Line Tool for LibSass." -HOMEPAGE="https://sass-lang.com/" -EGIT_PROJECT='sassc' -EGIT_REPO_URI="https://github.com/sass/sassc.git" -LICENSE="MIT" -SLOT="0" -KEYWORDS="" -IUSE="" -DEPEND="www-misc/libsass" -RDEPEND="${DEPEND}" -DEPEND="${DEPEND}" - -src_prepare() { - eautoreconf -} -``` diff --git a/docs/build-on-windows.md b/docs/build-on-windows.md index 458131909b..6aa762ad50 100644 --- a/docs/build-on-windows.md +++ b/docs/build-on-windows.md @@ -1,7 +1,7 @@ -We support builds via MingGW and via Visual Studio Community 2013. +We support builds via MinGW and via Visual Studio Community 2013. Both should be considered experimental (MinGW was better tested)! -## Building via MingGW (makefiles) +## Building via MinGW (makefiles) First grab the latest [MinGW for windows][1] installer. Once it is installed, you can click on continue or open the Installation Manager via `bin\mingw-get.exe`. @@ -24,7 +24,7 @@ As mentioned in the [MinGW Getting Started](http://www.mingw.org/wiki/Getting_St C:\MinGW /mingw ``` -### Starting a "MingGW" console +### Starting a "MinGW" console Create a batch file with this content: ```bat @@ -50,7 +50,8 @@ git clone https://github.com/sass/sass-spec.git libsass/sass-spec ### Decide for static or shared library -`libsass` can be built and linked as a `static` or as a `shared` library. The default is `static`. To change it you can set the `BUILD` environment variable: +`libsass` can be built and linked as a `static` or as a `shared` library. +The default is `static`. To change it you can set the `BUILD` environment variable: ```bat set BUILD="shared" @@ -72,7 +73,7 @@ libsass.a libsass.dll libsass.so mingw32-make -C libsass test_build ``` -## Building via MingGW 64bit (makefiles) +## Building via MinGW 64bit (makefiles) Building libass to dll on window 64bit. + downloads [MinGW64 for windows7 64bit](http://sourceforge.net/projects/mingw-w64/files/Toolchains%20targetting%20Win64/Personal%20Builds/mingw-builds/4.9.2/threads-win32/seh/x86_64-4.9.2-release-win32-seh-rt_v3-rev0.7z/download) , and unzip to "C:\mingw64". @@ -94,7 +95,7 @@ cmd ``` bash lib/libsass.dll: $(COBJECTS) $(OBJECTS) $(RCOBJECTS) $(MKDIR) lib - $(CXX) -shared $(LDFLAGS) -o $@ $(COBJECTS) $(OBJECTS) $(RCOBJECTS) $(LDLIBS) -s -static -Wl,--subsystem,windows,--out-implib,lib/libsass.a + $(CXX) -shared $(LDFLAGS) -o $@ $(COBJECTS) $(OBJECTS) $(RCOBJECTS) $(LDLIBS) -s -static -Wl,--subsystem,windows,--out-implib,lib/libsass.dll.a ``` + Compile the library diff --git a/docs/build-shared-library.md b/docs/build-shared-library.md index 3c143b46ae..83b91c10a8 100644 --- a/docs/build-shared-library.md +++ b/docs/build-shared-library.md @@ -1,9 +1,17 @@ -This page is mostly intended for people that want to build a system library that gets distributed via RPMs or other means. This is currently in a experimental phase, as we currently do not really guarantee any ABI forward compatibility. The C API was rewritten to make this possible in the future, but we want to wait some more time till we can call this final and stable. +This page is mostly intended for people that want to build a system library that +gets distributed via RPMs or other means. This is currently in a experimental +phase, as we currently do not really guarantee any ABI forward compatibility. +The C API was rewritten to make this possible in the future, but we want to +wait some more time till we can call this final and stable. Building via autotools -- -You want to build a system library only via autotools, since it will create the proper `libtool` files to make it loadable on multiple systems. We hope this works correctly, but nobody of the `libsass` core team has much knowledge in this area. Therefore we are open for comments or improvements by people that have more experience in that matter (like package maintainers from various linux distributions). +You want to build a system library only via autotools, since it will create the +proper `libtool` files to make it loadable on multiple systems. We hope this works +correctly, but nobody of the `libsass` core team has much knowledge in this area. +Therefore we are open for comments or improvements by people that have more experience +in that matter (like package maintainers from various linux distributions). ```bash apt-get install autoconf libtool @@ -23,13 +31,21 @@ This should install these files ```bash # $ ls -la /usr/lib/libsass.* /usr/lib/libsass.la -/usr/lib/libsass.so -> libsass.so.0.0.9 -/usr/lib/libsass.so.0 -> libsass.so.0.0.9 -/usr/lib/libsass.so.0.0.9 +/usr/lib/libsass.so -> libsass.so.2.0.0 +/usr/lib/libsass.so.0 -> libsass.so.2.0.0 +/usr/lib/libsass.so.2.0.0 # $ ls -la /usr/include/sass* /usr/include/sass.h -/usr/include/sass2scss.h -/usr/include/sass/context.h -/usr/include/sass/functions.h +/usr/include/sass/base.h +/usr/include/sass/compiler.h +/usr/include/sass/enums.h +/usr/include/sass/error.h +/usr/include/sass/function.h +/usr/include/sass/fwdecl.h +/usr/include/sass/import.h +/usr/include/sass/importer.h +/usr/include/sass/traces.h /usr/include/sass/values.h +/usr/include/sass/variable.h +/usr/include/sass/version.h ``` diff --git a/docs/build-with-autotools.md b/docs/build-with-autotools.md index a48ed18aa2..bc1e3f7a67 100644 --- a/docs/build-with-autotools.md +++ b/docs/build-with-autotools.md @@ -1,4 +1,5 @@ ### Get the sources + ```bash # using git is preferred git clone https://github.com/sass/libsass.git @@ -9,7 +10,8 @@ git clone https://github.com/sass/sass-spec.git libsass/sass-spec ### Prerequisites -In order to run autotools you need a few tools installed on your system. +In order to run autotools you need a few items installed on your system. + ```bash yum install automake libtool # RedHat Linux emerge -a automake libtool # Gentoo Linux @@ -18,6 +20,7 @@ pkgin install automake libtool # SmartOS ### Create configure script + ```bash cd libsass autoreconf --force --install @@ -25,6 +28,7 @@ cd .. ``` ### Create custom makefiles + ```bash cd libsass ./configure \ @@ -35,18 +39,25 @@ cd .. ``` ### Build the library + ```bash make -C libsass -j5 ``` ### Install the library -The library will be installed to the location given as `prefix` to `configure`. This is standard behavior for autotools and not `libsass` specific. + +The library will be installed to the location given as `prefix` to `configure`. +This is standard behavior for autotools and not `libsass` specific. + ```bash make -C libsass -j5 install ``` ### Configure options -The `configure` script is created by autotools. To get an overview of available options you can call `./configure --help`. When you execute this script, it will create specific makefiles, which you then use via the regular make command. + +The `configure` script is created by autotools. To get an overview of available +options you can call `./configure --help`. When you execute this script, it will +create specific makefiles, which you then use via the regular make command. There are some `libsass` specific options: diff --git a/docs/build-with-makefiles.md b/docs/build-with-makefiles.md index 7ae2e33d62..70167e5719 100644 --- a/docs/build-with-makefiles.md +++ b/docs/build-with-makefiles.md @@ -1,4 +1,5 @@ ### Get the sources + ```bash # using git is preferred git clone https://github.com/sass/libsass.git @@ -9,7 +10,8 @@ git clone https://github.com/sass/sass-spec.git libsass/sass-spec ### Decide for static or shared library -`libsass` can be built and linked as a `static` or as a `shared` library. The default is `static`. To change it you can set the `BUILD` environment variable: +`libsass` can be built and linked as a `static` or as a `shared` library. The +default is `static`. To change it you can set the `BUILD` environment variable: ```bash export BUILD="shared" @@ -38,6 +40,7 @@ We recommend to use [autotools to install](build-with-autotools.md) libsass onto system, since that brings all the benefits of using libtools as the main install method. If you still want to install libsass via the makefile, you need to make sure that gnu `install` utility (or compatible) is installed on your system. + ```bash yum install coreutils # RedHat Linux emerge -a coreutils # Gentoo Linux @@ -49,7 +52,6 @@ You can set the install location by setting `PREFIX` PREFIX="/opt/local" make install ``` - ### Compling sassc ```bash @@ -62,7 +64,7 @@ make -C libsass -j5 sassc ### Run the spec test-suite ```bash -# needs ruby available -# also gem install minitest +# needs ruby available, plus +# $ gem install hrx minitest make -C libsass -j5 test_build ``` diff --git a/docs/build-with-mingw.md b/docs/build-with-mingw.md index 416507f3c6..86da86934e 100644 --- a/docs/build-with-mingw.md +++ b/docs/build-with-mingw.md @@ -1,27 +1,36 @@ -## Building LibSass with MingGW (makefiles) +## Building LibSass with MinGW (makefiles) -First grab the latest [MinGW for windows][1] installer. Once it is installed, you can click on continue or open the Installation Manager via `bin\mingw-get.exe`. +First grab the latest [MinGW for windows][1] installer. Once it is installed, you can click +on continue or open the Installation Manager via `bin\mingw-get.exe`. Alternatively you may +install MinGW via [Chocolatey][5] or use the [MSYS2][6] or [Cygwin][7] compiler toolchain. +There should be plenty of information on the internet to setup a C++ tool-chain on windows. -You need to have the following components installed: +If you use the MinGW You need to have the following components installed: ![](https://cloud.githubusercontent.com/assets/282293/5525466/947bf396-89e6-11e4-841d-4aa916f14de1.png) -Next we need to install [git for windows][2]. You probably want to check the option to add it to the global path, but you do not need to install the unix tools. +Next we need to install [git for windows][2]. You probably want to check the option to add +it to the global path, but you do not need to install the unix tools. -If you want to run the spec test-suite you also need [ruby][3] and a few gems available. Grab the [latest installer][3] and make sure to add it the global path. Then install the missing gems: +If you want to run the spec test-suite you also need [ruby][3] and a few gems available. Grab the +[latest installer][3] and make sure to add it the global path. Then install the missing gems: ```bash +gem install hrx gem install minitest ``` +Note: the information below might be outdated by now (2021). + ### Mount the mingw root directory -As mentioned in the [MinGW Getting Started](http://www.mingw.org/wiki/Getting_Started#toc5) guide, you should edit `C:\MinGW\msys\1.0\etc\fstab` to contain the following line: +As mentioned in the [MinGW Getting Started](http://www.mingw.org/wiki/Getting_Started#toc5) guide, +you should edit `C:\MinGW\msys\1.0\etc\fstab` to contain the following line: ``` C:\MinGW /mingw ``` -### Starting a "MingGW" console +### Starting a "MinGW" console Create a batch file with this content: ```bat @@ -69,10 +78,10 @@ libsass.a libsass.dll libsass.so mingw32-make -C libsass test_build ``` -## Building via MingGW 64bit (makefiles) +## Building via MinGW 64bit (makefiles) Building libass to dll on window 64bit. -Download [MinGW64 for windows7 64bit](http://sourceforge.net/projects/mingw-w64/files/Toolchains%20targetting%20Win64/Personal%20Builds/mingw-builds/4.9.2/threads-win32/seh/x86_64-4.9.2-release-win32-seh-rt_v3-rev0.7z/download) and unzip to "C:\mingw64". +Download [MinGW64 for windows7 64bit][4] and unzip to "C:\mingw64". Create a batch file with this content: @@ -91,7 +100,7 @@ By default, mingw64 dll will depends on "​m​i​n​g​w​m​1​0​.​ ``` bash lib/libsass.dll: $(COBJECTS) $(OBJECTS) $(RCOBJECTS) $(MKDIR) lib - $(CXX) -shared $(LDFLAGS) -o $@ $(COBJECTS) $(OBJECTS) $(RCOBJECTS) $(LDLIBS) -s -static -Wl,--subsystem,windows,--out-implib,lib/libsass.a + $(CXX) -shared $(LDFLAGS) -o $@ $(COBJECTS) $(OBJECTS) $(RCOBJECTS) $(LDLIBS) -s -static -Wl,--subsystem,windows,--out-implib,lib/libsass.dll.a ``` Compile the library @@ -105,3 +114,7 @@ By the way, if you are using java jna, [JNAerator](http://jnaerator.googlecode.c [1]: http://sourceforge.net/projects/mingw/files/latest/download?source=files [2]: https://msysgit.github.io/ [3]: http://rubyinstaller.org/ +[4]: http://sourceforge.net/projects/mingw-w64/files/Toolchains%20targetting%20Win64/Personal%20Builds/mingw-builds/4.9.2/threads-win32/seh/x86_64-4.9.2-release-win32-seh-rt_v3-rev0.7z/download +[5]: https://community.chocolatey.org/packages/mingw +[6]: https://www.msys2.org/ +[7]: https://www.cygwin.com/ diff --git a/docs/build-with-visual-studio.md b/docs/build-with-visual-studio.md index 275b917b82..af6a8d09d4 100644 --- a/docs/build-with-visual-studio.md +++ b/docs/build-with-visual-studio.md @@ -1,90 +1,57 @@ ## Building LibSass with Visual Studio -### Requirements: +The minimum requirement to build LibSass with Visual Studio is currently +version 15.0 (Visual Studio 2017). Simply download and install [Visual +Studio from Microsoft](https://visualstudio.microsoft.com/downloads/). +The Community Edition is even free for open-source projects. -The minimum requirement to build LibSass with Visual Studio is "Visual Studio 2013 Express for Desktop". +Additionally, it is recommended to have `git` installed and available +via `PATH` env-variable, in order to deduce the `libsass` version info. +If `git` is not available, the LibSass version will be set to `[NA]`. -Additionally, it is recommended to have `git` installed and available in `PATH`, so to deduce the `libsass` version information. For instance, if GitHub for Windows (https://windows.github.com/) is installed, the `PATH` will have an entry resembling: `X:\Users\\AppData\Local\GitHub\PortableGit_\cmd\` (where `X` is the drive letter of system drive). If `git` is not available, inquiring the LibSass version will result in `[NA]`. +Once installed simply load `win/libsass.sln` file into Visual Studio. +Then build (Ctrl+Shift+B) any configuration you'd like. -### Build Steps: +[1]: https://visualstudio.microsoft.com/downloads/ -#### From Visual Studio: +### Building on command prompt -On opening the `win\libsass.sln` solution and build (Ctrl+Shift+B) to build `libsass.dll`. +In order to build with MSVC on the command line, you either need to bring +`msbuild.exe` into the common `PATH` folders or reference it via absolute path. +The easiest way is to open the `VS201X Tools Command Prompt`, which should be +a shortcuts installed alongside with Visual Studio. -To Build LibSass as a static Library, it is recommended to set an environment variable `LIBSASS_STATIC_LIB` before launching the project: - -```cmd -cd path\to\libsass -SET LIBSASS_STATIC_LIB=1 -:: -:: or in PowerShell: -:: $env:LIBSASS_STATIC_LIB=1 -:: -win\libsass.sln -``` - -Visual Studio will form the filtered source tree as shown below: - -![image](https://cloud.githubusercontent.com/assets/3840695/9298985/aae9e072-44bf-11e5-89eb-e7995c098085.png) - -`Header Files` contains the .h and .hpp files, while `Source Files` covers `.c` and `.cpp`. The other used headers/sources will appear under `External Dependencies`. - -If there is a LibSass code file appearing under External Dependencies, it can be changed by altering the `win\libsass.vcxproj.filters` file or dragging in Solution Explorer. - -#### From Command Prompt: - -Notice that in the following commands: +It is normally located at `"%ProgramFiles(x86)%\MSBuild\15.0\Bin\MSBuild"` * If the platform is 32-bit Windows, replace `ProgramFiles(x86)` with `ProgramFiles`. -* To build with Visual Studio 2015, replace `12.0` with `14.0` in the aforementioned command. +* To build with Visual Studio 2019, replace `15.0` with `16.0` in the commands. -Open a command prompt: +Once the command is available you can build any configuration you need: -To build dynamic/shared library (`libsass.dll`): - -```cmd -:: debug build: -"%ProgramFiles(x86)%\MSBuild\12.0\Bin\MSBuild" win\libsass.sln - -:: release build: -"%ProgramFiles(x86)%\MSBuild\12.0\Bin\MSBuild" win\libsass.sln ^ -/p:Configuration=Release -``` - -To build static library (`libsass.lib`): - -```cmd -:: debug build: -"%ProgramFiles(x86)%\MSBuild\12.0\Bin\MSBuild" win\libsass.sln ^ -/p:LIBSASS_STATIC_LIB=1 - -:: release build: -"%ProgramFiles(x86)%\MSBuild\12.0\Bin\MSBuild" win\libsass.sln ^ -/p:LIBSASS_STATIC_LIB=1 /p:Configuration=Release +```bash +MSBuild win\libsass.sln /p:Platform="x64" /p:Configuration="Release Shared" -t:Clean;Build +MSBuild win\libsass.sln /p:Platform="x86" /p:Configuration="Debug Static" -t:Clean;Build ``` -#### From PowerShell: +The results can be found inside the `build` directory. -To build dynamic/shared library (`libsass.dll`): +### Training LibSass for better performance -```powershell -# debug build: -&"${env:ProgramFiles(x86)}\MSBuild\12.0\Bin\MSBuild" win\libsass.sln +Visual Studio offers the possibility to train code via Profile Guided Optimizations. +In order to achieve this we need to first create an "instrumented" build. This build +will then generate statistics when being executed. Once enough data is generated, +the code is linked once more to create the final optimized version. You can expect +to get around 10% to 15% free performance. -# release build: -&"${env:ProgramFiles(x86)}\MSBuild\12.0\Bin\MSBuild" win\libsass.sln ` -/p:Configuration=Release +```bash +# Build an instrumented version to gather statistics for later optimization +MSBuild libsass.sln /p:Platform="x64" /p:Configuration="Release Shared" /p:PGO="Instrument" +# Run the trainer against some heavy sass benchmark code +# You may need to change into a different directory +..\build\x86\Release\Shared\trainer bench.scss 1>nul +# Build the final optimized version with the gathered profile statistics +MSBuild libsass.sln /p:Platform="x64" /p:Configuration="Release Shared" /p:PGO="Instrument" ``` -To build static library (`libsass.lib`): +See [train.bat](win/train.bat) for a full example. -```powershell -# build: -&"${env:ProgramFiles(x86)}\MSBuild\12.0\Bin\MSBuild" win\libsass.sln ` -/p:LIBSASS_STATIC_LIB=1 - -# release build: -&"${env:ProgramFiles(x86)}\MSBuild\12.0\Bin\MSBuild" win\libsass.sln ` -/p:LIBSASS_STATIC_LIB=1 /p:Configuration=Release -``` diff --git a/docs/build.md b/docs/build.md index 138ff1d8a4..fbbf437646 100644 --- a/docs/build.md +++ b/docs/build.md @@ -1,22 +1,39 @@ -`libsass` is only a library and does not do much on its own. You need an implementation that you can use from the [command line][6]. Or some [bindings|Implementations][9] to use it within your favorite programming language. You should be able to get [`sassc`][6] running by following the instructions in this guide. +## Building libsass from source -Before starting, see [setup dev environment](setup-environment.md). +LibSass is only a library and does not do much on its own, you'll need an additional +piece to tell LibSass what to do actually, called the implementor. The most basic +implementor is [`sassc`][6], as a basic command line interface (CLI). LibSass can +either be compiled as a static or shared library, and provides a [functional API][12] +to its consumer. Therefore it can be utilized to drive all kind of different +applications (CLI utility, bindings to other languages or even in browsers). -Building on different Operating Systems --- +This guide will try to follow you through the steps to get [`sassc`][6] running. -We try to keep the code as OS independent and standard compliant as possible. Reading files from the file-system has some OS depending code, but will ultimately fall back to a posix compatible implementation. We do use some `C++11` features, but are so far only committed to use `unordered_map`. This means you will need a pretty recent compiler on most systems (gcc 4.5 seems to be the minimum). +You need a working C++ compiler (e.g. gcc 4.8 or newer). If you are new to C++, +please consult the internet how to get a working compiler toolchain running on your +operating system, or see our guide to [setup dev environment](setup-environment.md). + +## Building on different Operating Systems + +We try to keep the LibSass as OS independent and standard compliant as possible. +Reading files from the file-system has some OS depending code, but will ultimately +fall back to a posix compatible implementation. We do use newer `C++11` features, +meaning you will need a recent compiler (gcc 4.7 being the current minimum). ### Building on Linux (and other *nix flavors) -Linux is the main target for `libsass` and we support two ways to build `libsass` here. The old plain makefiles should still work on most systems (including MinGW), while the autotools build is preferred if you want to create a [system library] (experimental). +Linux is the main target for `libsass` and we support two ways to build `libsass` here. +The old plain makefiles should still work on most systems (including MinGW), while the +autotools build is preferred if you want to create a [system library]. - [Building with makefiles][1] - [Building with autotools][2] -### Building on Windows (experimental) +### Building on Windows (mileage may vary) -Windows build support was added very recently and should be considered experimental. Credits go to @darrenkopp and @am11 for their work on getting `libsass` and `sassc` to compile with visual studio! +On windows you can either compile LibSass via MSVC or a posix compatible toolchain +like MinGW, MSYS2 or Cygwin. It should always compile fine with the latest compiler +toolchain provided by [Strawberry perl](https://strawberryperl.com/). - [Building with MinGW][3] - [Building with Visual Studio][11] @@ -29,26 +46,12 @@ Works the same as on linux, but you can also install LibSass via `homebrew`. ### Building a system library (experimental) -Since `libsass` is a library, it makes sense to install it as a shared library on your system. On linux this means creating a `.so` library via autotools. This should work pretty well already, but we are not yet committed to keep the ABI 100% stable. This should be the case once we increase the version number for the library to 1.0.0 or higher. On Windows you should be able get a `dll` by creating a shared build with MinGW. There is currently no target in the MSVC project files to do this. +Since `libsass` is a library, it makes sense to install it as a system-wide shared +library. On linux this means creating versioned `.so` library via autotools. - [Building shared system library][4] -### Building from vcpkg - -The libsass port in [vcpkg][12] is kept up to date by Microsoft team members and community contributors. You can download and install libsass using the vcpkg dependency manager: - -```bash -git clone https://github.com/Microsoft/vcpkg.git -cd vcpkg -./bootstrap-vcpkg.sh # ./bootstrap-vcpkg.bat for Windows -./vcpkg integrate install -./vcpkg install libsass -``` - -If the version is out of date, please [create an issue or pull request][12] on the vcpkg repository. - -Compiling with clang instead of gcc --- +#### Compiling with clang instead of gcc To use clang you just need to set the appropriate environment variables: @@ -57,29 +60,37 @@ export CC=/usr/bin/clang export CXX=/usr/bin/clang++ ``` -Running the spec test-suite --- +#### Running the spec test-suite + +We constantly and automatically test `libsass` against the official [spec test-suite][5]. +To do this we need to have a test-runner (which is written in ruby) and a command-line +tool ([`sassc`][6]) to run the tests. Therefore we need to additionally compile `sassc`. +To do this, the build files of all three projects need to work together. -We constantly and automatically test `libsass` against the official [spec test-suite][5]. To do this we need to have a test-runner (which is written in ruby) and a command-line tool ([`sassc`][6]) to run the tests. Therefore we need to additionally compile `sassc`. To do this, the build files of all three projects need to work together. This may not have the same quality for all build flavors. You definitely need to have ruby (2.1?) installed (version 1.9 seems to cause problems at least on windows). You also need some gems installed: +You also need some ruby gems installed: ```bash ruby -v +gem install hrx gem install minitest # should be optional gem install minitap ``` -Including the LibSass version --- +#### Including the LibSass version -There is a function in `libsass` to query the current version. This has to be defined at compile time. We use a C macro for this, which can be defined by calling `g++ -DLIBSASS_VERSION="\"x.y.z.\""`. The two quotes are necessary, since it needs to end up as a valid C string. Normally you do not need to do anything if you use the makefiles or autotools. They will try to fetch the version via git directly. If you only have the sources without the git repo, you can pass the version as an environment variable to `make` or `configure`: +There is a function in `libsass` to query the current version. This has to be defined at compile time. +We use a C macro for this, which can be defined by calling e.g. `g++ -DLIBSASS_VERSION="\"x.y.z.\""`. +The two quotes are necessary, since it needs to end up as a valid C string. Normally you do not +need to do anything if you use the makefiles or autotools. They will try to fetch the version +either via an optional VERSION file or via git directly. If you only have the sources without +the git repo, you can also pass the version as an environment variable to `make` or `configure`: ``` export LIBSASS_VERSION="x.y.z." ``` -Continuous Integration --- +#### Continuous Integration We use two CI services to automatically test all commits against the latest [spec test-suite][5]. @@ -88,15 +99,10 @@ We use two CI services to automatically test all commits against the latest [spe - [LibSass on AppVeyor (windows)][8] [![Build status](https://ci.appveyor.com/api/projects/status/github/sass/libsass?svg=true)](https://ci.appveyor.com/project/sass/libsass/branch/master) -Why not using CMake? --- - -There were some efforts to get `libsass` to compile with CMake, which should make it easier to create build files for linux and windows. Unfortunately this was not completed. But we are certainly open for PRs! - -Miscellaneous --- +#### Why not using CMake (or any other tool)? -- [Ebuilds for Gentoo Linux](build-on-gentoo.md) +There were some efforts to get `libsass` to compile with CMake, which should make it easier to +create build files for linux and windows. Unfortunately this was not completed. But we are certainly open for PRs! [1]: build-with-makefiles.md [2]: build-with-autotools.md @@ -109,4 +115,4 @@ Miscellaneous [9]: implementations.md [10]: build-on-darwin.md [11]: build-with-visual-studio.md -[12]: https://github.com/Microsoft/vcpkg +[12]: api-doc.md diff --git a/docs/contributing.md b/docs/contributing.md deleted file mode 100644 index 4a2d470ef1..0000000000 --- a/docs/contributing.md +++ /dev/null @@ -1,17 +0,0 @@ -First of all, welcome! Thanks for even reading this page. If you're here, you're probably wondering what you can do to help make the LibSass project even more awesome. And, even having that feeling means you are awesome! - -## I'm a programmer - -Awesome! We need your help. The best thing to do is go find issues that are tagged with both "bug" and "test written". We do spec driven development here and these issues have a test that's written already in the sass-spec project. Go find the test by going to sass-spec/spec/LibSass-todo-issues/issue_XXX/ where XXX is the issue number. Write the code, and compile, and then issue a pull request referencing the issue. We'll quickly verify it and get it merged in! - -To get your dev environment setup, check out our article on [Setup-Dev-Environment](setup-environment.md). - -## I'm not a backend programmer - -COOL! We also need your help. Doing [Issue-Triage](triage.md) is a big deal and something we need constant help with. That means helping to verify issues, write tests for them, and make sure they are getting fixed. It's being part of the smiling face of the project. - -Also, we need help with the Sass-Spec project itself. Just people to organize, refactor, and understand the tests in there. - -## I don't know what a computer is? - -Hmm.... well, it's the thing you are looking at right now. Ummm... check out training courses! Then, come back and join us! diff --git a/docs/custom-functions-internal.md b/docs/custom-functions-internal.md deleted file mode 100644 index 86784333b4..0000000000 --- a/docs/custom-functions-internal.md +++ /dev/null @@ -1,122 +0,0 @@ -# Developer Documentation - -Custom functions are internally represented by `struct Sass_C_Function_Descriptor`. - -## Sass_C_Function_Descriptor - -```C -struct Sass_C_Function_Descriptor { - const char* signature; - Sass_C_Function function; - void* cookie; -}; -``` - -- `signature`: The function declaration, like `foo($bar, $baz:1)` -- `function`: Reference to the C function callback -- `cookie`: any pointer you want to attach - -### signature - -The signature defines how the function can be invoked. It also declares which arguments are required and which are optional. Required arguments will be enforced by LibSass and a Sass error is thrown in the event a call as missing an argument. Optional arguments only need to be present when you want to overwrite the default value. - - foo($bar, $baz: 2) - -In this example, `$bar` is required and will error if not passed. `$baz` is optional and the default value of it is 2. A call like `foo(10)` is therefore equal to `foo(10, 2)`, while `foo()` will produce an error. - -### function - -The callback function needs to be of the following form: - -```C -union Sass_Value* call_sass_function( - const union Sass_Value* s_args, - void* cookie -) { - return sass_clone_value(s_args); -} -``` - -### 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 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 - -```C -// allocate memory (copies passed strings) -union Sass_Value* sass_make_null (void); -union Sass_Value* sass_make_boolean (bool val); -union Sass_Value* sass_make_string (const char* val); -union Sass_Value* sass_make_qstring (const char* val); -union Sass_Value* sass_make_number (double val, const char* unit); -union Sass_Value* sass_make_color (double r, double g, double b, double a); -union Sass_Value* sass_make_list (size_t len, enum Sass_Separator sep, bool is_bracketed); -union Sass_Value* sass_make_map (size_t len); -union Sass_Value* sass_make_error (const char* msg); -union Sass_Value* sass_make_warning (const char* msg); - -// Make a deep cloned copy of the given sass value -union Sass_Value* sass_clone_value (const union Sass_Value* val); - -// deallocate memory (incl. all copied memory) -void sass_delete_value (const union Sass_Value* val); -``` - -## Example main.c - -```C -#include -#include -#include "sass/context.h" - -union Sass_Value* call_fn_foo(const union Sass_Value* s_args, void* cookie) -{ - // we actually abuse the void* to store an "int" - return sass_make_number((size_t)cookie, "px"); -} - -int main( int argc, const char* argv[] ) -{ - - // get the input file from first argument or use default - const char* input = argc > 1 ? argv[1] : "styles.scss"; - - // create the file context and get all related structs - struct Sass_File_Context* file_ctx = sass_make_file_context(input); - struct Sass_Context* ctx = sass_file_context_get_context(file_ctx); - struct Sass_Options* ctx_opt = sass_context_get_options(ctx); - - // allocate a custom function caller - Sass_C_Function_Callback fn_foo = - sass_make_function("foo()", call_fn_foo, (void*)42); - - // create list of all custom functions - Sass_C_Function_List fn_list = sass_make_function_list(1); - sass_function_set_list_entry(fn_list, 0, fn_foo); - sass_option_set_c_functions(ctx_opt, fn_list); - - // context is set up, call the compile step now - int status = sass_compile_file_context(file_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_file_context(file_ctx); - - // exit status - return status; - -} -``` - -## Compile main.c - -```bash -gcc -c main.c -o main.o -gcc -o sample main.o -lsass -echo "foo { margin: foo(); }" > foo.scss -./sample foo.scss => "foo { margin: 42px }" -``` diff --git a/docs/allocator.md b/docs/dev-allocator.md similarity index 100% rename from docs/allocator.md rename to docs/dev-allocator.md diff --git a/docs/dev-ast-memory.md b/docs/dev-ast-memory.md index af872a89d0..1b1c944237 100644 --- a/docs/dev-ast-memory.md +++ b/docs/dev-ast-memory.md @@ -5,7 +5,6 @@ by Boost or C++11. Implementation is a bit less modular since it was not needed. Various compile time debug options are available if you need to debug memory life-cycles. - ## Memory Classes ### SharedObj @@ -64,7 +63,6 @@ when the reference counted object goes out of scope, it will not free the attached memory. You are now again in charge of freeing the memory (just assign it to a reference counted object again). - ## Circular references Reference counted memory implementations are prone to circular references. @@ -80,7 +78,6 @@ complete object clones to these members. If you know the objects lifetime is longer than the reference you create, you can also just store the raw pointer. Once needed this could be solved with weak pointers. - ## Addressing the invalid covariant return types problems If you are not familiar with the mentioned problem, you may want @@ -220,4 +217,4 @@ profound advantages: As said above, this is not thread safe currently. But we don't need this ATM anyway. And I guess we probably never will share AST Nodes -across different threads. \ No newline at end of file +across different threads. diff --git a/docs/dev-env-stacks.md b/docs/dev-env-stacks.md new file mode 100644 index 0000000000..cb1fecec03 --- /dev/null +++ b/docs/dev-env-stacks.md @@ -0,0 +1,223 @@ +# How LibSass handles variables, functions and mixins + +This document is intended for developers of LibSass only and are of no use +for implementers. It documents how variable stacks are implemented. + +## Foreword + +LibSass uses an optimized stack approach similar to how C compilers have always +done it, by using a growable stack where we can push and pop items. Unfortunately +Sass has proven to be a bit more dynamic than static optimizers like, therefore we +had to adopt the principle a little to accommodate the edge-cases due to this. + +There are three different kind of entities on the stack during runtime, namely +variables, functions and mixins. Each has its own dedicated stack to optimize +the lookups. In this doc we will often only cover one case, but it should be +applicable to any other stack object (with some small differences). Variables +are the most complicated ones, as functions and mixins can only be declared +on the root scope, so loops or functions don't need to be considered for them. + +Also for regular sass code and style-rules we wouldn't need this setup, but +it becomes essential to correctly support mixins and functions, since those +can be called recursively. It is also vital for loops, like `@for` or `@each`. + +## Overview + +The whole process is split into two main phases. In order to correctly support +`@import` we had to introduce the preloader phase, where all `@use`, `@forward` and +`@import` rules are loaded first, before any evaluation happens. This ensures that +we know all entities before the evaluation phase in order to correctly setup +all stack frames before populating them. + +### Parser/EnvFrame phase + +During parsing every potential scope block creates an `EnvFrame`, which is +stored at the corresponding ast-node (e.g. on a `StyleRule`). The `EnvFrame` +is designed as a RAII stack object. It adds itself to the frame-stack on +creation and removes itself once it goes out of scope. Additionally it +creates an `EnvRefs` instance on the heap, which is later used by the +compile/evaluation phase. + +### Compiler/EnvScope phase + +The `EnvScope` is similar to the `EnvFrame`, as it is also designed to be a RAII +stack object. On creation it will increase the stack for each entity and update +the corresponding offset pointers and reverts it when it goes out of scope. + +### EnvRefs heap object + +The `EnvRefs` object is the main object holding all the information about a +block scope. It mainly holds three (flat) maps, one for every entity type. +These maps are used to resolve a name to an integer offset. Whenever a new +entity (e.g. variable assignment), the item is added to the map if it does +not already exist there and the offset is simply increased (size of the map). + +### EnvRoot object + +The `EnvRoot` is the main object where entities are stored. It mainly holds +two stack vectors for every entity type. The actual stack vector and an +additional vector holding the previous stack size (or offset position). +Further it holds on to all created `EnvRefs` and all built-in entities. + +### Frame offset pointers + +Every `EnvRefs` object get a unique number, increasing from 0 (which is +reserved for the root scope block). The `EnvRoot` objects has a vector +containing all `EnvRefs` which should correspond to that index offset. + +## Basic example + +Let's assume we have the following scss code: + +```scss +$a: a; +b { + $a: b; +} +``` + +### Parsing phase + +The parser will first initialize the `EnvRoot` with the root `EnvRefs`. +First it will parse the top variable assignment, which will create a new +entry in the variable map of the `EnvRefs` heap object. + +It will then parse the `StyleRule` and create a `EnvFrame` (which will also +create and register a new `EnvRefs` heap object). It will then parse the inner +assignment and create a new entry in the new `EnvRefs` of the `StyleRule`. + +### Evaluation phase + +First the compiler will create an `EnvScope` stack object, which will increase +the stack size of `EnvRoot` accordingly. In this case it will increase the size +of `varStack` by one to accommodate the single local variable. Note that the +variable object itself is still undefined at this point, but the slot on the +stack exists now. The `EnvScope` will also remember that it has to decrease +that stack vector by one, once it goes out of scope. + +On the assignment the compiler knows that variable `$a` can be found at the +local offset `0` (as stored within the `EnvRefs` map). Now it only needs to +add the current `EnvRefs` position on the `varStack` to get the absolute +address of the requested variable. + + + + when it sees `b`, it will +create and assign a new `EnvFrame` with the `StyleRule`. Each `EnvRefs` +will have one Variable `$a` with the offset `0`. On runtime the compiler will +first evaluate the top assignment rule, thus assigning the string `a` to the +variable at offset `0` at the current active scope (root). Then it evaluates +the ruleset and creates a new `EnvScope`, which will push new instances onto +the env-stack for all previously parsed entities (one variable in this case). + +We now have two variables on the actual env-scope with the inner still undefined. + + +This will allocate two independent variables on the stack. For easier reference +we can think of them as variable 0 and variable 1. So let's see what happens if +we introduce some VariableExpressions: + +```scss +$a: 1; +b { + a0: $a; + $a: 2; + a1: $a; +} +c { + a: $a; +} +``` + +As you may have guesses, the `a0` expression will reference variable 0 and the +`a1` expression will reference variable 1, while the last one will reference +variable 0 again. Given this easy example this might seem overengineered, but +let's see what happens if we introduce a loop: + +```scss +$a: 1; +b { + @for $x from 1 through 2 { + a0: $a; + $a: 2; + a1: $a; + } +} +c { + a: $a; +} +``` + +Here I want to concentrate on `a0`. In most programing languages, `a0: $a` would +always point to variable 0, but in Sass this is more dynamic. It will actually +reference variable 0 on the first run, and variable 1 on consecutive runs. + +## What is an EnvFrame and EnvRef + +Whenever we encounter a new scope while parsing, we will create a new EnvFrame. +Every EnvFrame (often also just called idxs) knows the variables, functions and +mixins that are declared within that scope. Each entity is simply referenced by +it's integer offset (first variable, second variable and so on). Each frame is +stored as long as the context/compiler lives. In order to find functions, each +frame keeps a hash-map to get the local offset for an entity name (e.g. varIdxs). +An EnvRef is just a struct with the env-frame address and the local entity offset. + +## Where are entities actually stored during runtime + +The `EnvRoot` has a growable stack for each entity type. Whenever we evaluate +a lexical scope, we will push the entities to the stack to bring them live. +By doing this, we also update the current pointer for the given env-frame to +point to the correct position within that stack. Let's see how this works: + +```scss +$a: 1; +@function recursive($abort) { + $a: $a + 1; + @if ($abort) { + @return $a; + } + @else { + @return recursive(true); + } +} +a { + b: recursive(false); +} +``` + +Here we call the recursive function twice, so the `$a` inside must be independent. +The stack allocation would look the following in this case: + +- Entering root scope + - pushing one variable on the runtime var stack. +- Entering for scope for the first time + - updating varFramePtr to 1 since there is already one variable. + - pushing another variable on the runtime var stack +- Entering for scope for the second time + - updating varFramePtr to 2 since there are now two variable. + - pushing another variable on the runtime var stack +- Exiting second for scope and restoring old state +- Exiting first for scope and restoring old state +- Exiting root scope + +So in the second for loop run, when we have to resolve the variable expression for `$a`, +we first get the base frame pointer (often called stack frame pointer in C). Then we only +need to add the local offset to get to the current frame instance of the variable. Once we +exit a scope, we simply need to pop those entities off the stack and reset the frame pointer. + +## How ambiguous/dynamic lookup is done in loops + +Unfortunately we are not able to fully statically optimize the variable lookups (as explained +earlier, due to the full dynamic nature of Sass). IMO we can do it for functions and mixins, +as they always have to be declared and defined at the same time. But variables can also just +be declared and defined later. So in order to optimize this situation we will first fetch and +cache all possible variable declarations (vector of VarRef). Then on evaluation we simply need +to check and return the first entity that was actually defined (assigned to). + +## Afterword + +I hope this example somehow clarifies how variable stacks are implemented in LibSass. This +optimization can easily bring 50% or more performance in contrast to always do the dynamic +lookup via the also available hash-maps. They are needed anyway for meta functions, like +`function-exists`. It is also not possible to (easily) create new variables once the parsing +is done, so the C-API doesn't allow to create new variables during runtime. diff --git a/docs/dev-filewatcher.md b/docs/dev-filewatcher.md new file mode 100644 index 0000000000..18218934f4 --- /dev/null +++ b/docs/dev-filewatcher.md @@ -0,0 +1,19 @@ +## LibSass and file-watchers + +Currently and in the foreseeable future LibSass will not support any file-watching mode. +Such a feature was always intended to be implemented by downstream consumers. In order +to support this feature, LibSass compilation phase is split into different phases. + +LibSass supports to query all files included in a specific compilation. With the help +of this list, consumers can setup file-watching for all involved files. But note that +this list can change on any change, as the user may add or remove imports. This has to +be checked by consumers between different compilations. Normally the flow would look +something like this: + +- Setup compiler with entry point +- Get initial list of relevant files +- Optionally compile it when starting-up +- On any change in any of the given files + - Recompile the entry point and write result + - Get the involved file-list of the entry-point + - Re-initialize watcher if relevant files changed diff --git a/docs/dev-plugins.md b/docs/dev-plugins.md new file mode 100644 index 0000000000..8a647c19fa --- /dev/null +++ b/docs/dev-plugins.md @@ -0,0 +1,53 @@ +# LibSass plugins + +Plugins are shared object files (.so on *nix and .dll on win) that can be +loaded by LibSass on runtime. Each plugin must have a main entry point +`libsass_init_plugin(struct SassCompiler* compiler)`. There you can add +new variables, custom functions or do anything you want with the compiler. + +## plugin.cpp + +```C++ +#include +#include +#include +#include + +struct SassValue* ADDCALL call_fn_foo(struct SassValue* s_args, struct SassCompiler* comp, void* cookie) +{ + // we actually abuse the void* to store an "int" + return sass_make_number((intptr_t)cookie, "px"); +} + +extern "C" const char* ADDCALL libsass_get_version() { + return libsass_version(); +} + +// entry point for libsass to request custom functions from plugin +extern "C" void ADDCALL libsass_init_plugin(struct SassCompiler* compiler) +{ + + // Add constants via custom headers + sass_compiler_add_custom_function(compiler, + sass_make_function("foo()", call_fn_foo, (void*)42)); +} +``` + +To compile the plugin you need to have LibSass already built as a shared library +(to link against it). The commands below expect the shared library in the `lib` +sub-directory (`-Llib`). The plugin and the main LibSass process should "consume" +the same shared LibSass library on runtime. It will probably also work if they +use different LibSass versions. In this case we check if the major versions are +compatible (i.e. 3.1.3 and 3.1.1 would be considered compatible). + +## Compile with gcc on linux + +```bash +g++ -O2 -shared plugin.cpp -o plugin.so -fPIC -Llib -lsass +``` + +## Compile with mingw on windows + +```bash +g++ -O2 -shared plugin.cpp -o plugin.dll -Llib -lsass +``` diff --git a/docs/dev-unicode.md b/docs/dev-unicode.md new file mode 100644 index 0000000000..6eb929fa5f --- /dev/null +++ b/docs/dev-unicode.md @@ -0,0 +1,86 @@ +## LibSass and Unicode + +Note: this was written quite a long time ago, and while most of the information +here is still valid, some items have evolved a bit in the meantime. The main +show-stopper for LibSass to fully support unicode and e.g Latin1 files as input +is the lack of unicode conversion tables (e.g. libiconv). + +LibSass currently expects all input to be utf8 encoded (and outputs only utf8), +if you actually have any unicode characters at all. We do not support conversion +between encodings, even if you declare it with a `@charset` rule. The text below +was originally posted as an [issue](https://github.com/sass/libsass/issues/381) +on the LibSass tracker. Since then the status is outdated as LibSass now expects +your input to be utf8/ascii compatible, as it has been proven that reading ANSI +(e.g. single byte encodings) as utf8 can lead to unexpected behavior, which can +in the worst case lead to buffer overruns/segfaults. Therefore LibSass now checks +your input to be valid utf8 encoded! + +### [Declaring character encodings in CSS](http://www.w3.org/International/questions/qa-css-charset.en) + +This [explains](http://www.w3.org/International/questions/qa-css-charset.en) how +the character encoding of a css file is determined. Since we are only dealing with +local files, we never have a HTTP header. So the precedence should be 'charset' rule, +byte-order mark (BOM) or auto-detection (finally falling back to system default/UTF-8). +This may not sound too hard to implement, but what about import rules? The CSS specs do +not forbid the mixing of different encodings! I [solved that](https://github.com/mgreter/webmerge/) by +converting all files to UTF-8 internally. On writing there is an option to tell the tool +what encoding it should be (UTF-8 by default). One can also define if it should write a +BOM or not and if it should add the charset declaration. A similar issue also popped up +on the [drupal issue tracker](https://www.drupal.org/project/drupal/issues/1833356). + +Since my [tool]((https://github.com/mgreter/webmerge/)) is written in perl, I have a lot of +utilities at hand to deal with different unicode charsets. I'm pretty sure that most OSS +uses [ICU](http://site.icu-project.org/) or [libiconv](https://www.gnu.org/software/libiconv/) to +convert between different encodings. But I have now idea how easy/hard this would be to +integrate platform independent (it seems doable). ANSII (single byte encoding) to utf8 +is basically just a conversion table (for every supported code-page). + +### Current status on LibSass unicode support + +LibSass should/is fully UTF (and therefore plain ASCII) compatible. + +~~Currently LibSass seems to handle the common UTF-8 case pretty well. I believe it should correctly +support all ASCII compatible encodings (like UTF-8 or Latin-1). If all includes use the same encoding, +the output should be correct (in the same encoding). It should also handle unicode chars in [selectors, +variable names and other identifiers](https://github.com/hcatlin/libsass/issues/244#issuecomment-34681227). +This is true for all ASCII compatible encodings. So the main incompatible encodings (I'm aware of) are +UTF-16/UTF-32 (which could be converted to UTF-8 with libiconv).~~ + +LibSass 3.5 will enforce that your input is either plain ASCII (chars below 127) or utf8. It does not handle +anything else, but therefore ensures that the output is in a valid form. Before version 3.5 you were able to +mix different code-pages, which yielded unexpected behavior. + +### Current encoding auto detection + +LibSass currently reads all kind of BOMs and will error out if it finds something it doesn't know how to +handle! It seems that it throws away the optional UTF-8 BOM (if any is found). IMO it would be nice if +users could configure that (also if a charset rule should be added to the output). But it does not really +take any `@charset` into account, it always assumes your input is utf8 and ignores any given `@charset`! + +### What is currently not supported + +- Using non ASCII compatible encodings (like UTF-16, Latin-1 etc.) +- Using non ASCII characters in different encodings in different includes + +### What is missing to support the above cases + +- A way to convert between encodings (like libiconv/ICU) +- Sniffing the charset inside the file (source is available) +- Handling the conversion on import (and export) +- Optional: Make output encoding configurable +- Optional: Add optional/mandatory BOM (configurable) + +### Low priority feature + +I guess the current implementation should handle more than 99% of all real world use cases. +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!~~ + +I guess the biggest Problem is to have libiconv/ICU (or some other) library as a dependency. Since it contains +a lot of rules for the conversions, I see it as the only way to handle this correctly. Once that is sorted out +it should be pretty much straight forward to implement the missing pieces (in parser.cpp - Parser::parse should +return encoding and add Parser::sniff_charset, then convert the source byte stream to UTF-8). + +I hope the statements above all hold true. Unicode is really not the easiest topic to wrap your head around. But +since I did all the above recently in Perl, I wanted to document it here. Feel free to extend or criticize. diff --git a/docs/dev-var-scoping.md b/docs/dev-var-scoping.md new file mode 100644 index 0000000000..bed3762f90 --- /dev/null +++ b/docs/dev-var-scoping.md @@ -0,0 +1,57 @@ +# Sass variable scoping + +Variable scoping in Sass has some quirks which I'll try to +explain in this document. This partially also applies to +functions and mixins, as they are scope objects like variables +too. But they can only be defined on the root scope, so their +logic is not that complicated as variables. + +## Variable declarations in Sass + +Sass doesn't have explicit variable declarations. Variables +are declared whenever an assignment to a variable happens. It +is then local to the scope block the assignment was in. E.g. +every ruleset (opened via `{` in scss) is a new scope block. + +On the surface this makes variable scoping quite simple, as +declarations always happen with the definition of the variable. +Under the hood it has some quirks, mainly in combination with loops. + +## Block scopes and (semi) transparent loops + +Consider the following example: + +```scss +$a: 0; +@for $i from 1 through 3 { + @debug $a; + $a: $i; +} +@debug $a +``` + +The first `@debug` calls should be easy to guess (0,1,2). But the +last `@debug` is a bit trickier. In this case the `@for` loop is +transparent and the assignment inside it is made to the outer +variable on the root scope and will be reported as `3`. + +Now lets wrap the loop into a ruleset scope: + +```scss +$b: 0; +a { + @for $i from 1 through 3 { + @debug $b; + $b: $i; + } + @debug $b +} +``` + +Again, the inner `@debug` calls are simply 0,1 and 2 and the +tricky question is again what the last `@debug` call will yield? +You might have guess it will report `3` again, but that is not +correct, as Sass will report `0` here. + +Conclusion: Loops like `@for`, `@each` and `@while` are transparent +only on the root-scope, but not on any inner scopes. diff --git a/docs/developing.md b/docs/developing.md index cb94253c8c..3351030180 100644 --- a/docs/developing.md +++ b/docs/developing.md @@ -2,10 +2,10 @@ So far this is only a loose collection of developer relevant docs: +- [C-API documentation](api-doc.md) - [Building LibSass](build.md) +- [LibSass Plugins](dev-plugins.md) - [Profiling LibSass](dev-profiling.md) -- [C-API documentation](api-doc.md) -- [LibSass and Unicode](unicode.md) -- [SourceMap internals](source-map-internals.md) -- [Custom memory allocator](allocator.md) +- [LibSass and Unicode](dev-unicode.md) +- [Custom memory allocator](dev-allocator.md) - [Smart pointer implementation](dev-ast-memory.md) diff --git a/docs/implementations-3.md b/docs/implementations-3.md new file mode 100644 index 0000000000..95febc1444 --- /dev/null +++ b/docs/implementations-3.md @@ -0,0 +1,68 @@ +There are several implementations of `libsass` for a variety of languages. Here are just a few of them. Note, some implementations may or may not be up to date. We have not verified whether they work. + +### C +* [sassc](https://github.com/hcatlin/sassc) + +### Crystal +* [sass.cr](https://github.com/straight-shoota/sass.cr) + +### Elixir +* [sass.ex](https://github.com/scottdavis/sass.ex) +* [sass_compiler](https://github.com/Youimmi/sass_compiler) + +### Go +* [go-libsass](https://github.com/wellington/go-libsass) +* [go_sass](https://github.com/suapapa/go_sass) +* [go-sass](https://github.com/SamWhited/go-sass) + +### Haskell +* [hLibsass](https://github.com/jakubfijalkowski/hlibsass) +* [hSass](https://github.com/jakubfijalkowski/hsass) + +### Java +* [libsass-maven-plugin](https://github.com/warmuuh/libsass-maven-plugin) +* [jsass](https://github.com/bit3/jsass) + +### JavaScript +* [sass.js](https://github.com/medialize/sass.js) + +### Lua +* [lua-sass](https://github.com/craigbarnes/lua-sass) + +### .NET +* [libsass-net](https://github.com/darrenkopp/libsass-net) +* [NSass](https://github.com/TBAPI-0KA/NSass) +* [Sass.Net](https://github.com/andyalm/Sass.Net) +* [SharpScss](https://github.com/xoofx/SharpScss) +* [LibSassHost](https://github.com/Taritsyn/LibSassHost) + +### Nim + +* [sass](https://github.com/dom96/sass) +* [nim-sass](https://github.com/zacharycarter/nim-sass) + +### node.js +* [node-sass](https://github.com/sass/node-sass) + +### Perl +* [CSS::Sass](https://github.com/caldwell/CSS-Sass) +* [Text::Sass::XS](https://github.com/ysasaki/Text-Sass-XS) + +### PHP +* [sassphp](https://github.com/sensational/sassphp) +* [php-sass](https://github.com/lesstif/php-sass) + +### Python +* [libsass-python](https://github.com/dahlia/libsass-python) +* [SassPython](https://github.com/marianoguerra/SassPython) +* [pylibsass](https://github.com/rsenk330/pylibsass) +* [python-scss](https://github.com/pistolero/python-scss) + +### Ruby +* [sassruby](https://github.com/hcatlin/sassruby) + +### Scala +* [Sass-Scala](https://github.com/kkung/Sass-Scala) + +### Tcl +* [tclsass](https://github.com/flightaware/tclsass) diff --git a/docs/implementations.md b/docs/implementations.md index 95febc1444..009d49c112 100644 --- a/docs/implementations.md +++ b/docs/implementations.md @@ -1,68 +1,13 @@ -There are several implementations of `libsass` for a variety of languages. Here are just a few of them. Note, some implementations may or may not be up to date. We have not verified whether they work. +# LibSass implementations -### C -* [sassc](https://github.com/hcatlin/sassc) - -### Crystal -* [sass.cr](https://github.com/straight-shoota/sass.cr) - -### Elixir -* [sass.ex](https://github.com/scottdavis/sass.ex) -* [sass_compiler](https://github.com/Youimmi/sass_compiler) - -### Go -* [go-libsass](https://github.com/wellington/go-libsass) -* [go_sass](https://github.com/suapapa/go_sass) -* [go-sass](https://github.com/SamWhited/go-sass) - -### Haskell -* [hLibsass](https://github.com/jakubfijalkowski/hlibsass) -* [hSass](https://github.com/jakubfijalkowski/hsass) - -### Java -* [libsass-maven-plugin](https://github.com/warmuuh/libsass-maven-plugin) -* [jsass](https://github.com/bit3/jsass) - -### JavaScript -* [sass.js](https://github.com/medialize/sass.js) - -### Lua -* [lua-sass](https://github.com/craigbarnes/lua-sass) +There are several implementations of `libsass` for a variety of languages. +Here list of implementations compatible with the latest LibSass version. +There is also an [older list with implementation][1] for the previous 3 branch. -### .NET -* [libsass-net](https://github.com/darrenkopp/libsass-net) -* [NSass](https://github.com/TBAPI-0KA/NSass) -* [Sass.Net](https://github.com/andyalm/Sass.Net) -* [SharpScss](https://github.com/xoofx/SharpScss) -* [LibSassHost](https://github.com/Taritsyn/LibSassHost) - -### Nim - -* [sass](https://github.com/dom96/sass) -* [nim-sass](https://github.com/zacharycarter/nim-sass) - -### node.js -* [node-sass](https://github.com/sass/node-sass) +### C +* [sassc](https://github.com/sass/sassc) ### Perl * [CSS::Sass](https://github.com/caldwell/CSS-Sass) -* [Text::Sass::XS](https://github.com/ysasaki/Text-Sass-XS) - -### PHP -* [sassphp](https://github.com/sensational/sassphp) -* [php-sass](https://github.com/lesstif/php-sass) - -### Python -* [libsass-python](https://github.com/dahlia/libsass-python) -* [SassPython](https://github.com/marianoguerra/SassPython) -* [pylibsass](https://github.com/rsenk330/pylibsass) -* [python-scss](https://github.com/pistolero/python-scss) - -### Ruby -* [sassruby](https://github.com/hcatlin/sassruby) - -### Scala -* [Sass-Scala](https://github.com/kkung/Sass-Scala) -### Tcl -* [tclsass](https://github.com/flightaware/tclsass) +[1]: implementations-3.md diff --git a/docs/plugins.md b/docs/plugins.md deleted file mode 100644 index a9711e3e16..0000000000 --- a/docs/plugins.md +++ /dev/null @@ -1,47 +0,0 @@ -Plugins are shared object files (.so on *nix and .dll on win) that can be loaded by LibSass on runtime. Currently we only provide a way to load internal/custom functions from plugins. In the future we probably will also add a way to provide custom importers via plugins (needs more refactoring to [support multiple importers with some kind of priority system](https://github.com/sass/libsass/issues/962)). - -## plugin.cpp - -```C++ -#include -#include -#include -#include "sass_values.h" - -union Sass_Value* ADDCALL call_fn_foo(const union Sass_Value* s_args, void* cookie) -{ - // we actually abuse the void* to store an "int" - return sass_make_number((intptr_t)cookie, "px"); -} - -extern "C" const char* ADDCALL libsass_get_version() { - return libsass_version(); -} - -extern "C" Sass_C_Function_List ADDCALL libsass_load_functions() -{ - // allocate a custom function caller - Sass_C_Function_Callback fn_foo = - sass_make_function("foo()", call_fn_foo, (void*)42); - // create list of all custom functions - Sass_C_Function_List fn_list = sass_make_function_list(1); - // put the only function in this plugin to the list - sass_function_set_list_entry(fn_list, 0, fn_foo); - // return the list - return fn_list; -} -``` - -To compile the plugin you need to have LibSass already built as a shared library (to link against it). The commands below expect the shared library in the `lib` sub-directory (`-Llib`). The plugin and the main LibSass process should "consume" the same shared LibSass library on runtime. It will propably also work if they use different LibSass versions. In this case we check if the major versions are compatible (i.e. 3.1.3 and 3.1.1 would be considered compatible). - -## Compile with gcc on linux - -```bash -g++ -O2 -shared plugin.cpp -o plugin.so -fPIC -Llib -lsass -``` - -## Compile with mingw on windows - -```bash -g++ -O2 -shared plugin.cpp -o plugin.dll -Llib -lsass -``` diff --git a/docs/setup-environment.md b/docs/setup-environment.md index e30845b6e2..beaedc74c8 100644 --- a/docs/setup-environment.md +++ b/docs/setup-environment.md @@ -3,7 +3,7 @@ In order to install and setup your local development environment, there are some * git * gcc/clang/llvm (Linux: build tools, Mac OS X: XCode w/ Command Line Tools) -* ruby w/ bundler +* ruby w/ bundler (only to run the spec-test suite) OS X: First you'll need to install XCode which you can now get from the AppStore installed on your mac. After you download that and run it, then run this on the command line: diff --git a/docs/source-map-internals.md b/docs/source-map-internals.md deleted file mode 100644 index fd829a198a..0000000000 --- a/docs/source-map-internals.md +++ /dev/null @@ -1,51 +0,0 @@ -This document is mainly intended for developers! - -# Documenting some of the source map internals - -Since source maps are somewhat a black box to all LibSass maintainers, [I](@mgreter) will try to document my findings with source maps in LibSass, as I come across them. This document will also brievely explain how LibSass parses the source and how it outputs the result. - -The main storage for SourceMap mappings is the `mappings` vector: - -``` -# in source_map.hpp -vector mappings -# in mappings.hpp -struct Mapping ... - Position original_position; - Position generated_position; -``` - -## Every parsed token has its source associated - -LibSass uses a lexical parser. Whenever LibSass finds a token of interest, it creates a specific `AST_Node`, which will hold a reference to the input source with line/column information. `AST_Node` is the base class for all parsed items. They are declared in `ast.hpp` and are used in `parser.hpp`. Here a simple example: - -``` -if (lex< custom_property_name >()) { - Sass::String* prop = new (ctx.mem) String_Constant(path, source_position, lexed); - return new (ctx.mem) Declaration(path, prop->position(), prop, ...); -} -``` - -## 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 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 > >(); -end = new (ctx.mem) String_Constant(path, source_position, lexed); -``` - -## How are mappings for the output created - -So far we have collected all needed data for all tokens in the input stream. We can now use this information to create mappings when we put things into the output stream. Mappings are created via the `add_mappings` method: - -``` -# in source_map.hpp -void add_mapping(AST_Node* node); -``` - -This method is called in two places: -- `Inspect::append_to_buffer` -- `Output_[Nested|Compressed]::append_to_buffer` - -Mappings can only be created for things that have been parsed into a `AST_Node`. Otherwise we do not have the information to create the mappings, which is the reason why LibSass currently only maps the most important tokens in source maps. diff --git a/docs/trace.md b/docs/trace.md deleted file mode 100644 index 4a57c901f9..0000000000 --- a/docs/trace.md +++ /dev/null @@ -1,26 +0,0 @@ -## This is proposed interface in https://github.com/sass/libsass/pull/1288 - -Additional debugging macros with low overhead are available, `TRACE()` and `TRACEINST()`. - -Both macros simulate a string stream, so they can be used like this: - - TRACE() << "Reached."; - -produces: - - [LibSass] parse_value parser.cpp:1384 Reached. - -`TRACE()` - logs function name, source filename, source file name to the standard error and the attached - stream to the standard error. - -`TRACEINST(obj)` - logs object instance address, function name, source filename, source file name to the standard error and the attached stream to the standard error, for example: - - TRACEINST(this) << "String_Constant created " << this; - -produces: - - [LibSass] 0x8031ba980:String_Constant ./ast.hpp:1371 String_Constant created (0,"auto") - -The macros generate output only of `LibSass_TRACE` is set in the environment. diff --git a/docs/triage.md b/docs/triage.md deleted file mode 100644 index 0fc11784cc..0000000000 --- a/docs/triage.md +++ /dev/null @@ -1,17 +0,0 @@ -This is an article about how to help with LibSass issues. Issue triage is a fancy word for explaining how we deal with incoming issues and make sure that the right problems get worked on. The lifecycle of an issue goes like this: - -1. Issue is reported by a user. -2. If the issue seems like a bug, then the "bug" tag is added. -3. If the reporting user didn't also create a spec test over at sass/sass-spec, the "needs test" tag is added. -4. Verify that Ruby Sass *does not* have the same bug. LibSass strives to be an exact replica of how Ruby Sass works. If it's an issue that neither project has solved, please close the ticket with the "not in sass" label. -5. The smallest possible breaking test is created in sass-spec. Cut away any extra information or non-breaking code until the core issue is made clear. -6. Again, verify that the expected output matches the latest Ruby Sass release. Do this by using your own tool OR by running ./sass-spec.rb in the spec folder and making sure that your test passes! -7. Create the test cases in sass-spec with the name spec/LibSass-todo-issues/issue_XXX/input.scss and expected_output.css where the XXX is the issue number here. -8. Commit that test to sass-spec, making sure to reference the issue in the comment message like "Test to demonstrate sass/LibSass#XXX". -9. Once the spec test exists, remove the "needs test" tag and replace it with "test written". -10. A C++ developer will then work on the issue and issue a pull request to fix the issue. -11. A core member verifies that the fix does actually fix the spec tests. -12. The fix is merged into the project. -13. The spec is moved from the LibSass-todo-issues folder into LibSass-closed-issues -14. The issue is closed -15. Have a soda pop or enjoyable beverage of your choice diff --git a/docs/unicode.md b/docs/unicode.md deleted file mode 100644 index 309d70e449..0000000000 --- a/docs/unicode.md +++ /dev/null @@ -1,45 +0,0 @@ -LibSass currently expects all input to be utf8 encoded (and outputs only utf8), if you actually have any unicode characters at all. We do not support conversion between encodings, even if you declare it with a `@charset` rule. The text below was originally posted as an [issue](https://github.com/sass/libsass/issues/381) on the LibSass tracker. Since then the status is outdated as LibSass now expects your -input to be utf8/ascii compatible, as it has been proven that reading ANSI (e.g. single byte encodings) as utf8 can lead to unexpected -behavior, which can in the worst case lead to buffer overruns/segfaults. Therefore LibSass now checks your input to be valid utf8 encoded! - -### [Declaring character encodings in CSS](http://www.w3.org/International/questions/qa-css-charset.en) - -This [explains](http://www.w3.org/International/questions/qa-css-charset.en) how the character encoding of a css file is determined. Since we are only dealing with local files, we never have a HTTP header. So the precedence should be 'charset' rule, byte-order mark (BOM) or auto-detection (finally falling back to system default/UTF-8). This may not sound too hard to implement, but what about import rules? The CSS specs do not forbid the mixing of different encodings! I [solved that](https://github.com/mgreter/webmerge/) by converting all files to UTF-8 internally. On writing there is an option to tell the tool what encoding it should be (UTF-8 by default). One can also define if it should write a BOM or not and if it should add the charset declaration. - -Since my [tool]((https://github.com/mgreter/webmerge/)) is written in perl, I have a lot of utilities at hand to deal with different unicode charsets. I'm pretty sure that most OSS uses [ICU](http://site.icu-project.org/) or [libiconv](https://www.gnu.org/software/libiconv/) to convert between different encodings. But I have now idea how easy/hard this would be to integrate platform independent (it seems doable). ANSII (single byte encoding) to utf8 is basically just a conversion table (for every supported code-page). - -### Current status on LibSass unicode support - -LibSass should/is fully UTF (and therefore plain ASCII) compatible. - -~~Currently LibSass seems to handle the common UTF-8 case pretty well. I believe it should correctly support all ASCII compatible encodings (like UTF-8 or Latin-1). If all includes use the same encoding, the output should be correct (in the same encoding). It should also handle unicode chars in [selectors, variable names and other identifiers](https://github.com/hcatlin/libsass/issues/244#issuecomment-34681227). This is true for all ASCII compatible encodings. So the main incompatible encodings (I'm aware of) are UTF-16/UTF-32 (which could be converted to UTF-8 with libiconv).~~ - -LibSass 3.5 will enforce that your input is either plain ASCII (chars below 127) or utf8. It does not handle anything else, but therefore ensures that the output is in a valid form. Before version 3.5 you were able to mix different code-pages, which yielded unexpected behavior. - -### Current encoding auto detection - -LibSass currently reads all kind of BOMs and will error out if it finds something it doesn't know how to handle! It seems that it throws away the optional UTF-8 BOM (if any is found). IMO it would be nice if users could configure that (also if a charset rule should be added to the output). But it does not really take any `@charset` into account, it always assumes your input is utf8 and ignores any given `@charset`! - -### What is currently not supported - -- Using non ASCII compatible encodings (like UTF-16, Latin-1 etc.) -- Using non ASCII characters in different encodings in different includes - -### What is missing to support the above cases - -- A way to convert between encodings (like libiconv/ICU) -- Sniffing the charset inside the file (source is available) -- Handling the conversion on import (and export) -- Optional: Make output encoding configurable -- Optional: Add optional/mandatory BOM (configurable) - -### Low priority feature - -I guess the current implementation should handle more than 99% of all real world use cases. -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!~~ - -I guess the biggest Problem is to have libiconv/ICU (or some other) library as a dependency. Since it contains a lot of rules for the conversions, I see it as the only way to handle this correctly. Once that is sorted out it should be pretty much straight forward to implement the missing pieces (in parser.cpp - Parser::parse should return encoding and add Parser::sniff_charset, then convert the source byte stream to UTF-8). - -I hope the statements above all hold true. Unicode is really not the easiest topic to wrap your head around. But since I did all the above recently in Perl, I wanted to document it here. Feel free to extend or criticize. diff --git a/include/sass.h b/include/sass.h index 1dd8b06dca..ef07d10ccf 100644 --- a/include/sass.h +++ b/include/sass.h @@ -1,15 +1,22 @@ #ifndef SASS_H #define SASS_H -// #define DEBUG 1 +// Note: we can't forward declare with inheritance +// https://stackoverflow.com/a/10145303/1550314 // include API headers #include +#include +#include #include +#include +#include +#include #include -#include -#include -#include +#include +#include +#include +#include +#include #endif - diff --git a/include/sass/base.h b/include/sass/base.h index 132da693ab..1d93cf6d80 100644 --- a/include/sass/base.h +++ b/include/sass/base.h @@ -1,9 +1,9 @@ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ #ifndef SASS_BASE_H #define SASS_BASE_H -// #define DEBUG -// #define DEBUG_SHARED_PTR - #ifdef _MSC_VER #pragma warning(disable : 4503) #ifndef _SCL_SECURE_NO_WARNINGS @@ -23,9 +23,17 @@ #define noexcept throw( ) #endif +// Load some POD types #include +#include #include +// Include forward declarations +#include + +// Include enumerations +#include + #ifdef __GNUC__ #define DEPRECATED(func) func __attribute__ ((deprecated)) #elif defined(_MSC_VER) @@ -37,7 +45,7 @@ #ifdef _WIN32 - /* You should define ADD_EXPORTS *only* when building the DLL. */ +/* You should define ADD_EXPORTS *only* when building the DLL. */ #ifdef ADD_EXPORTS #define ADDAPI __declspec(dllexport) #define ADDCALL __cdecl @@ -54,41 +62,39 @@ #endif -/* Make sure functions are exported with C linkage under C++ compilers. */ #ifdef __cplusplus extern "C" { #endif + // Change the virtual current working directory + ADDAPI void ADDCALL sass_chdir(const char* path); + + // Prints message to stderr with color for windows + ADDAPI void ADDCALL sass_print_stdout(const char* message); + ADDAPI void ADDCALL sass_print_stderr(const char* message); + + // Return implemented sass language version + ADDAPI const char* ADDCALL libsass_version(void); + + // Return the compiled libsass language (hard-coded) + // This is hard-coded with the library on compilation! + ADDAPI const char* ADDCALL libsass_language_version(void); + + // Allocate a memory block on the heap of (at least) [size]. + // Make sure to release to acquired memory at some later point via + // `sass_free_memory`. You need to go through my utility function in + // case your code and my main program don't use the same memory manager. + ADDAPI void* ADDCALL sass_alloc_memory(size_t size); + + // Allocate a memory block on the heap and copy [string] into it. + // Make sure to release to acquired memory at some later point via + // `sass_free_memory`. You need to go through my utility function in + // case your code and my main program don't use the same memory manager. + ADDAPI char* ADDCALL sass_copy_c_string(const char* str); -// Different render styles -enum Sass_Output_Style { - SASS_STYLE_NESTED, - SASS_STYLE_EXPANDED, - SASS_STYLE_COMPACT, - SASS_STYLE_COMPRESSED, - // only used internaly - SASS_STYLE_INSPECT, - SASS_STYLE_TO_SASS, - SASS_STYLE_TO_CSS -}; - -// to allocate buffer to be filled -ADDAPI void* ADDCALL sass_alloc_memory(size_t size); -// to allocate a buffer from existing string -ADDAPI char* ADDCALL sass_copy_c_string(const char* str); -// to free overtaken memory when done -ADDAPI void ADDCALL sass_free_memory(void* ptr); - -// Some convenient string helper function -ADDAPI char* ADDCALL sass_string_quote (const char* str, const char quote_mark); -ADDAPI char* ADDCALL sass_string_unquote (const char* str); - -// Implemented sass language version -// Hardcoded version 3.4 for time being -ADDAPI const char* ADDCALL libsass_version(void); - -// Get compiled libsass language -ADDAPI const char* ADDCALL libsass_language_version(void); + // Deallocate libsass heap memory + ADDAPI void ADDCALL sass_free_memory(void* ptr); + ADDAPI void ADDCALL sass_free_c_string(char* ptr); #ifdef __cplusplus } // __cplusplus defined. diff --git a/include/sass/compiler.h b/include/sass/compiler.h new file mode 100644 index 0000000000..36a40fa4d8 --- /dev/null +++ b/include/sass/compiler.h @@ -0,0 +1,212 @@ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#ifndef SASS_C_COMPILER_H +#define SASS_C_COMPILER_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // Create a new LibSass compiler context + ADDAPI struct SassCompiler* ADDCALL sass_make_compiler(); + + // Release all memory allocated with the compiler + ADDAPI void ADDCALL sass_delete_compiler(struct SassCompiler* compiler); + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // Parse the entry point and potentially all imports within. + ADDAPI void ADDCALL sass_compiler_parse(struct SassCompiler* compiler); + + // Evaluate the parsed entry point and store resulting ast-tree. + ADDAPI void ADDCALL sass_compiler_compile(struct SassCompiler* compiler); + + // Render the evaluated ast-tree to get the final output string. + ADDAPI void ADDCALL sass_compiler_render(struct SassCompiler* compiler); + + // Write or print the output to the console or the configured output path + ADDAPI void ADDCALL sass_compiler_write_output(struct SassCompiler* compiler); + + // Write source-map to configured path if options are set accordingly + ADDAPI void ADDCALL sass_compiler_write_srcmap(struct SassCompiler* compiler); + + // Execute all compiler steps and write/print results + ADDAPI int ADDCALL sass_compiler_execute(struct SassCompiler* compiler); + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // Add additional include paths where LibSass will look for includes. + // Note: the passed in `paths` can be path separated (`;` on windows, `:` otherwise). + ADDAPI void ADDCALL sass_compiler_add_include_paths(struct SassCompiler* compiler, const char* paths); + + // Load dynamic loadable plugins from `paths`. Plugins are only supported on certain OSs and + // are still in experimental state. This will look for `*.dll`, `*.so` or `*.dynlib` files. + // It then tries to load the found libraries and does a few checks to see if the library + // is actually a LibSass plugin. We then call its init hook if the library is compatible. + // Note: the passed in `paths` can be path separated (`;` on windows, `:` otherwise). + ADDAPI void ADDCALL sass_compiler_load_plugins(struct SassCompiler* compiler, const char* paths); + + // Add a custom header importer that will always be executed before any other + // compilations takes place. Useful to prepend a shared copyright header or to + // provide global variables or functions. This feature is still in experimental state. + // Note: With the adaption of Sass Modules this might be completely replaced in the future. + ADDAPI void ADDCALL sass_compiler_add_custom_header(struct SassCompiler* compiler, struct SassImporter* header); + + // Add a custom importer that will be executed when a sass `@import` rule is found. + // This is useful to e.g. rewrite import locations or to load content from remote. + // For more please check https://github.com/sass/libsass/blob/master/docs/api-importer.md + // Note: The importer will not be called for regular css `@import url()` rules. + ADDAPI void ADDCALL sass_compiler_add_custom_importer(struct SassCompiler* compiler, struct SassImporter* importer); + + // Add a custom function that will be executed when the corresponding function call is + // requested from any sass code. This is useful to provide custom functions in your code. + // For more please check https://github.com/sass/libsass/blob/master/docs/api-function.md + ADDAPI void ADDCALL sass_compiler_add_custom_function(struct SassCompiler* compiler, struct SassFunction* function); + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // Setter for output style (see `enum SassOutputStyle` for possible options). + ADDAPI void ADDCALL sass_compiler_set_input_syntax(struct SassCompiler* compiler, enum SassImportSyntax syntax); + + // Setter for output style (see `enum SassOutputStyle` for possible options). + ADDAPI void ADDCALL sass_compiler_set_output_style(struct SassCompiler* compiler, enum SassOutputStyle style); + + // Try to detect and set logger options for terminal colors, unicode and columns. + ADDAPI void ADDCALL sass_compiler_autodetect_logger_capabilities(struct SassCompiler* compiler); + + // Setter for enabling/disabling logging with ANSI colors. + ADDAPI void ADDCALL sass_compiler_set_logger_colors(struct SassCompiler* compiler, bool enable); + + // Setter for enabling/disabling logging with unicode text. + ADDAPI void ADDCALL sass_compiler_set_logger_unicode(struct SassCompiler* compiler, bool enable); + + // Getter for number precision (how floating point numbers are truncated). + ADDAPI int ADDCALL sass_compiler_get_precision(struct SassCompiler* compiler); + + // Setter for number precision (how floating point numbers are truncated). + ADDAPI void ADDCALL sass_compiler_set_precision(struct SassCompiler* compiler, int precision); + + // Getter for compiler entry point (which file or data to parse first). + ADDAPI struct SassImport* ADDCALL sass_compiler_get_entry_point(struct SassCompiler* compiler); + + // Setter for compiler entry point (which file or data to parse first). + ADDAPI void ADDCALL sass_compiler_set_entry_point(struct SassCompiler* compiler, struct SassImport* import); + + // Getter for compiler output path (where to store the result) + // Note: LibSass does not write the file, implementers should write to this path. + ADDAPI const char* ADDCALL sass_compiler_get_output_path(struct SassCompiler* compiler); + + // Setter for compiler output path (where to store the result) + // Note: LibSass does not write the file, implementers should write to this path. + ADDAPI void ADDCALL sass_compiler_set_output_path(struct SassCompiler* compiler, const char* output_path); + + // Getter for option to suppress anything being printed on stderr (quiet mode) + ADDAPI bool ADDCALL sass_compiler_get_suppress_stderr(struct SassCompiler* compiler); + + // Setter for option to suppress anything being printed on stderr (quiet mode) + ADDAPI void ADDCALL sass_compiler_set_suppress_stderr(struct SassCompiler* compiler, bool suppress); + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // Getter for warnings that occurred during any step. + ADDAPI const char* ADDCALL sass_compiler_get_warn_string(struct SassCompiler* compiler); + + // Getter for output after parsing, compilation and rendering. + ADDAPI const char* ADDCALL sass_compiler_get_output_string(struct SassCompiler* compiler); + + // Getter for footer string containing optional source-map (embedded or link). + ADDAPI const char* ADDCALL sass_compiler_get_footer_string(struct SassCompiler* compiler); + + // Getter for string containing the optional source-mapping. + ADDAPI const char* ADDCALL sass_compiler_get_srcmap_string(struct SassCompiler* compiler); + + // Check if implementor is expected to write a output file + ADDAPI bool ADDCALL sass_compiler_has_output_file(struct SassCompiler* compiler); + + // Check if implementor is expected to write a source-map file + ADDAPI bool ADDCALL sass_compiler_has_srcmap_file(struct SassCompiler* compiler); + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // Setter for source-map mode (how to embed or not embed the source-map). + ADDAPI void ADDCALL sass_compiler_set_srcmap_mode(struct SassCompiler* compiler, enum SassSrcMapMode mode); + + // Setter for source-map path (where to store the source-mapping). + // Note: if path is not explicitly given, we will deduct one from output path. + // Note: LibSass does not write the file, implementers should write to this path. + ADDAPI void ADDCALL sass_compiler_set_srcmap_path(struct SassCompiler* compiler, const char* path); + + // Getter for source-map path (where to store the source-mapping). + // Note: if path is not explicitly given, we will deduct one from output path. + // Note: the value will only be deducted after the main render phase is completed. + // Note: LibSass does not write the file, implementers should write to this path. + ADDAPI const char* ADDCALL sass_compiler_get_srcmap_path(struct SassCompiler* compiler); + + // Setter for source-map root (simply passed to the resulting srcmap info). + // Note: if not given, no root attribute will be added to the srcmap info object. + ADDAPI void ADDCALL sass_compiler_set_srcmap_root(struct SassCompiler* compiler, const char* root); + + // Setter for source-map file-url option (renders urls in srcmap as `file://` urls) + ADDAPI void ADDCALL sass_compiler_set_srcmap_file_urls(struct SassCompiler* compiler, bool enable); + + // Setter for source-map embed-contents option (includes full sources in the srcmap info) + ADDAPI void ADDCALL sass_compiler_set_srcmap_embed_contents(struct SassCompiler* compiler, bool enable); + + // Setter to enable more detailed source map (also meaning bigger payload). + // Mostly useful if you want to post process the results again where the more detailed + // source-maps might by used by downstream post-processor to point back to original files. + ADDAPI void ADDCALL sass_compiler_set_srcmap_details(struct SassCompiler* compiler, bool openers, bool closers); + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // Getter to return the number of all included files. + ADDAPI size_t ADDCALL sass_compiler_get_included_files_count(struct SassCompiler* compiler); + + // Getter to return path to the included file at position `n`. + ADDAPI const char* ADDCALL sass_compiler_get_included_file_path(struct SassCompiler* compiler, size_t n); + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // Getter for current import context. Use `SassImport` functions to query the state. + ADDAPI const struct SassImport* ADDCALL sass_compiler_get_last_import(struct SassCompiler* compiler); + + // Returns pointer to error object associated with compiler. + // Will be valid until the associated compiler is destroyed. + ADDAPI const struct SassError* ADDCALL sass_compiler_get_error(struct SassCompiler* compiler); + + // Returns status code for compiler (0 meaning success, anything else is an error) + ADDAPI int ADDCALL sass_compiler_get_status(struct SassCompiler* compiler); + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // Resolve a file relative to last import or include paths in the sass option struct. + ADDAPI char* ADDCALL sass_compiler_find_file(const char* path, struct SassCompiler* compiler); + + // Resolve an include relative to last import or include paths in the sass option struct. + // This will do a lookup as LibSass would do internally (partials, different extensions). + // ToDo: Check if we should add `includeIndex` option to check for directory index files!? + ADDAPI char* ADDCALL sass_compiler_find_include(const char* path, struct SassCompiler* compiler); + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + +#ifdef __cplusplus +} // EO extern "C". +#endif + +#endif diff --git a/include/sass/context.h b/include/sass/context.h deleted file mode 100644 index 7eb181e259..0000000000 --- a/include/sass/context.h +++ /dev/null @@ -1,174 +0,0 @@ -#ifndef SASS_C_CONTEXT_H -#define SASS_C_CONTEXT_H - -#include -#include -#include -#include -#include - -#ifdef __cplusplus -extern "C" { -#endif - - -// Forward declaration -struct Sass_Compiler; - -// Forward declaration -struct Sass_Options; // base struct -struct Sass_Context; // : Sass_Options -struct Sass_File_Context; // : Sass_Context -struct Sass_Data_Context; // : Sass_Context - -// Compiler states -enum Sass_Compiler_State { - SASS_COMPILER_CREATED, - SASS_COMPILER_PARSED, - SASS_COMPILER_EXECUTED -}; - -// Create and initialize an option struct -ADDAPI struct Sass_Options* ADDCALL sass_make_options (void); -// Create and initialize a specific context -ADDAPI struct Sass_File_Context* ADDCALL sass_make_file_context (const char* input_path); -ADDAPI struct Sass_Data_Context* ADDCALL sass_make_data_context (char* source_string); - -// Call the compilation step for the specific context -ADDAPI int ADDCALL sass_compile_file_context (struct Sass_File_Context* ctx); -ADDAPI int ADDCALL sass_compile_data_context (struct Sass_Data_Context* ctx); - -// Create a sass compiler instance for more control -ADDAPI struct Sass_Compiler* ADDCALL sass_make_file_compiler (struct Sass_File_Context* file_ctx); -ADDAPI struct Sass_Compiler* ADDCALL sass_make_data_compiler (struct Sass_Data_Context* data_ctx); - -// Execute the different compilation steps individually -// Useful if you only want to query the included files -ADDAPI int ADDCALL sass_compiler_parse(struct Sass_Compiler* compiler); -ADDAPI int ADDCALL sass_compiler_execute(struct Sass_Compiler* compiler); - -// Release all memory allocated with the compiler -// This does _not_ include any contexts or options -ADDAPI void ADDCALL sass_delete_compiler(struct Sass_Compiler* compiler); -ADDAPI void ADDCALL sass_delete_options(struct Sass_Options* options); - -// Release all memory allocated and also ourself -ADDAPI void ADDCALL sass_delete_file_context (struct Sass_File_Context* ctx); -ADDAPI void ADDCALL sass_delete_data_context (struct Sass_Data_Context* ctx); - -// Getters for context from specific implementation -ADDAPI struct Sass_Context* ADDCALL sass_file_context_get_context (struct Sass_File_Context* file_ctx); -ADDAPI struct Sass_Context* ADDCALL sass_data_context_get_context (struct Sass_Data_Context* data_ctx); - -// Getters for Context_Options from Sass_Context -ADDAPI struct Sass_Options* ADDCALL sass_context_get_options (struct Sass_Context* ctx); -ADDAPI struct Sass_Options* ADDCALL sass_file_context_get_options (struct Sass_File_Context* file_ctx); -ADDAPI struct Sass_Options* ADDCALL sass_data_context_get_options (struct Sass_Data_Context* data_ctx); -ADDAPI void ADDCALL sass_file_context_set_options (struct Sass_File_Context* file_ctx, struct Sass_Options* opt); -ADDAPI void ADDCALL sass_data_context_set_options (struct Sass_Data_Context* data_ctx, struct Sass_Options* opt); - - -// Getters for Context_Option values -ADDAPI int ADDCALL sass_option_get_precision (struct Sass_Options* options); -ADDAPI enum Sass_Output_Style ADDCALL sass_option_get_output_style (struct Sass_Options* options); -ADDAPI bool ADDCALL sass_option_get_source_comments (struct Sass_Options* options); -ADDAPI bool ADDCALL sass_option_get_source_map_embed (struct Sass_Options* options); -ADDAPI bool ADDCALL sass_option_get_source_map_contents (struct Sass_Options* options); -ADDAPI bool ADDCALL sass_option_get_source_map_file_urls (struct Sass_Options* options); -ADDAPI bool ADDCALL sass_option_get_omit_source_map_url (struct Sass_Options* options); -ADDAPI bool ADDCALL sass_option_get_is_indented_syntax_src (struct Sass_Options* options); -ADDAPI const char* ADDCALL sass_option_get_indent (struct Sass_Options* options); -ADDAPI const char* ADDCALL sass_option_get_linefeed (struct Sass_Options* options); -ADDAPI const char* ADDCALL sass_option_get_input_path (struct Sass_Options* options); -ADDAPI const char* ADDCALL sass_option_get_output_path (struct Sass_Options* options); -ADDAPI const char* ADDCALL sass_option_get_source_map_file (struct Sass_Options* options); -ADDAPI const char* ADDCALL sass_option_get_source_map_root (struct Sass_Options* options); -ADDAPI Sass_Importer_List ADDCALL sass_option_get_c_headers (struct Sass_Options* options); -ADDAPI Sass_Importer_List ADDCALL sass_option_get_c_importers (struct Sass_Options* options); -ADDAPI Sass_Function_List ADDCALL sass_option_get_c_functions (struct Sass_Options* options); - -// Setters for Context_Option values -ADDAPI void ADDCALL sass_option_set_precision (struct Sass_Options* options, int precision); -ADDAPI void ADDCALL sass_option_set_output_style (struct Sass_Options* options, enum Sass_Output_Style output_style); -ADDAPI void ADDCALL sass_option_set_source_comments (struct Sass_Options* options, bool source_comments); -ADDAPI void ADDCALL sass_option_set_source_map_embed (struct Sass_Options* options, bool source_map_embed); -ADDAPI void ADDCALL sass_option_set_source_map_contents (struct Sass_Options* options, bool source_map_contents); -ADDAPI void ADDCALL sass_option_set_source_map_file_urls (struct Sass_Options* options, bool source_map_file_urls); -ADDAPI void ADDCALL sass_option_set_omit_source_map_url (struct Sass_Options* options, bool omit_source_map_url); -ADDAPI void ADDCALL sass_option_set_is_indented_syntax_src (struct Sass_Options* options, bool is_indented_syntax_src); -ADDAPI void ADDCALL sass_option_set_indent (struct Sass_Options* options, const char* indent); -ADDAPI void ADDCALL sass_option_set_linefeed (struct Sass_Options* options, const char* linefeed); -ADDAPI void ADDCALL sass_option_set_input_path (struct Sass_Options* options, const char* input_path); -ADDAPI void ADDCALL sass_option_set_output_path (struct Sass_Options* options, const char* output_path); -ADDAPI void ADDCALL sass_option_set_plugin_path (struct Sass_Options* options, const char* plugin_path); -ADDAPI void ADDCALL sass_option_set_include_path (struct Sass_Options* options, const char* include_path); -ADDAPI void ADDCALL sass_option_set_source_map_file (struct Sass_Options* options, const char* source_map_file); -ADDAPI void ADDCALL sass_option_set_source_map_root (struct Sass_Options* options, const char* source_map_root); -ADDAPI void ADDCALL sass_option_set_c_headers (struct Sass_Options* options, Sass_Importer_List c_headers); -ADDAPI void ADDCALL sass_option_set_c_importers (struct Sass_Options* options, Sass_Importer_List c_importers); -ADDAPI void ADDCALL sass_option_set_c_functions (struct Sass_Options* options, Sass_Function_List c_functions); - - -// Getters for Sass_Context values -ADDAPI const char* ADDCALL sass_context_get_output_string (struct Sass_Context* ctx); -ADDAPI int ADDCALL sass_context_get_error_status (struct Sass_Context* ctx); -ADDAPI const char* ADDCALL sass_context_get_error_json (struct Sass_Context* ctx); -ADDAPI const char* ADDCALL sass_context_get_error_text (struct Sass_Context* ctx); -ADDAPI const char* ADDCALL sass_context_get_error_message (struct Sass_Context* ctx); -ADDAPI const char* ADDCALL sass_context_get_error_file (struct Sass_Context* ctx); -ADDAPI const char* ADDCALL sass_context_get_error_src (struct Sass_Context* ctx); -ADDAPI size_t ADDCALL sass_context_get_error_line (struct Sass_Context* ctx); -ADDAPI size_t ADDCALL sass_context_get_error_column (struct Sass_Context* ctx); -ADDAPI const char* ADDCALL sass_context_get_source_map_string (struct Sass_Context* ctx); -ADDAPI char** ADDCALL sass_context_get_included_files (struct Sass_Context* ctx); - -// Getters for options include path array -ADDAPI size_t ADDCALL sass_option_get_include_path_size(struct Sass_Options* options); -ADDAPI const char* ADDCALL sass_option_get_include_path(struct Sass_Options* options, size_t i); -// Plugin paths to load dynamic libraries work the same -ADDAPI size_t ADDCALL sass_option_get_plugin_path_size(struct Sass_Options* options); -ADDAPI const char* ADDCALL sass_option_get_plugin_path(struct Sass_Options* options, size_t i); - -// Calculate the size of the stored null terminated array -ADDAPI size_t ADDCALL sass_context_get_included_files_size (struct Sass_Context* ctx); - -// Take ownership of memory (value on context is set to 0) -ADDAPI char* ADDCALL sass_context_take_error_json (struct Sass_Context* ctx); -ADDAPI char* ADDCALL sass_context_take_error_text (struct Sass_Context* ctx); -ADDAPI char* ADDCALL sass_context_take_error_message (struct Sass_Context* ctx); -ADDAPI char* ADDCALL sass_context_take_error_file (struct Sass_Context* ctx); -ADDAPI char* ADDCALL sass_context_take_error_src (struct Sass_Context* ctx); -ADDAPI char* ADDCALL sass_context_take_output_string (struct Sass_Context* ctx); -ADDAPI char* ADDCALL sass_context_take_source_map_string (struct Sass_Context* ctx); -ADDAPI char** ADDCALL sass_context_take_included_files (struct Sass_Context* ctx); - -// Getters for Sass_Compiler options -ADDAPI enum Sass_Compiler_State ADDCALL sass_compiler_get_state(struct Sass_Compiler* compiler); -ADDAPI struct Sass_Context* ADDCALL sass_compiler_get_context(struct Sass_Compiler* compiler); -ADDAPI struct Sass_Options* ADDCALL sass_compiler_get_options(struct Sass_Compiler* compiler); -ADDAPI size_t ADDCALL sass_compiler_get_import_stack_size(struct Sass_Compiler* compiler); -ADDAPI Sass_Import_Entry ADDCALL sass_compiler_get_last_import(struct Sass_Compiler* compiler); -ADDAPI Sass_Import_Entry ADDCALL sass_compiler_get_import_entry(struct Sass_Compiler* compiler, size_t idx); -ADDAPI size_t ADDCALL sass_compiler_get_callee_stack_size(struct Sass_Compiler* compiler); -ADDAPI Sass_Callee_Entry ADDCALL sass_compiler_get_last_callee(struct Sass_Compiler* compiler); -ADDAPI Sass_Callee_Entry ADDCALL sass_compiler_get_callee_entry(struct Sass_Compiler* compiler, size_t idx); - -// Push function for paths (no manipulation support for now) -ADDAPI void ADDCALL sass_option_push_plugin_path (struct Sass_Options* options, const char* path); -ADDAPI void ADDCALL sass_option_push_include_path (struct Sass_Options* options, const char* path); - -// Resolve a file via the given include paths in the sass option struct -// find_file looks for the exact file name while find_include does a regular sass include -ADDAPI char* ADDCALL sass_find_file (const char* path, struct Sass_Options* opt); -ADDAPI char* ADDCALL sass_find_include (const char* path, struct Sass_Options* opt); - -// Resolve a file relative to last import or include paths in the sass option struct -// find_file looks for the exact file name while find_include does a regular sass include -ADDAPI char* ADDCALL sass_compiler_find_file (const char* path, struct Sass_Compiler* compiler); -ADDAPI char* ADDCALL sass_compiler_find_include (const char* path, struct Sass_Compiler* compiler); - -#ifdef __cplusplus -} // __cplusplus defined. -#endif - -#endif diff --git a/include/sass/enums.h b/include/sass/enums.h new file mode 100644 index 0000000000..1cc51e769a --- /dev/null +++ b/include/sass/enums.h @@ -0,0 +1,56 @@ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#ifndef SASS_ENUMS_H +#define SASS_ENUMS_H + +#ifdef __cplusplus +extern "C" { +#endif + + // Different render styles + enum SassOutputStyle { + SASS_STYLE_NESTED, + SASS_STYLE_EXPANDED, + SASS_STYLE_COMPACT, + SASS_STYLE_COMPRESSED, + // only used internally! + SASS_STYLE_TO_CSS + }; + + // Type of parser to use + enum SassImportSyntax { + SASS_IMPORT_AUTO, + SASS_IMPORT_SCSS, + SASS_IMPORT_SASS, + SASS_IMPORT_CSS, + }; + + // Config how to produce source-map + enum SassSrcMapMode { + // Don't render any source-mapping. + SASS_SRCMAP_NONE, + // Only render the `srcmap` string. + // The `footer` will be `NULL`. + SASS_SRCMAP_CREATE, + // Write srcmap link into `footer` + SASS_SRCMAP_EMBED_LINK, + // Embed srcmap into `footer` + SASS_SRCMAP_EMBED_JSON, + }; + + // State of the compiler object + enum SassCompilerState { + SASS_COMPILER_CREATED, + SASS_COMPILER_PARSED, + SASS_COMPILER_COMPILED, + SASS_COMPILER_RENDERED, + SASS_COMPILER_DESTROYED, + SASS_COMPILER_FAILED, + }; + +#ifdef __cplusplus +} // __cplusplus defined. +#endif + +#endif diff --git a/include/sass/error.h b/include/sass/error.h new file mode 100644 index 0000000000..8c6ae8caab --- /dev/null +++ b/include/sass/error.h @@ -0,0 +1,62 @@ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#ifndef SASS_ERROR_H +#define SASS_ERROR_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // Error related getters (use after compiler was rendered) + ADDAPI int ADDCALL sass_error_get_status(const struct SassError* error); + + // Getter for plain error message (use after compiler was rendered). + ADDAPI const char* ADDCALL sass_error_get_string(const struct SassError* error); + + // Getter for error status as css (In order to show error in browser). + // Memory returned by this function must be freed via `sass_free_c_string`. + ADDAPI char* ADDCALL sass_error_get_css(const struct SassError* error); + + // Getter for error status as json object (Useful to pass to downstream). + // Memory returned by this function must be freed via `sass_free_c_string`. + ADDAPI char* ADDCALL sass_error_get_json(const struct SassError* error); + + // Getter for formatted error message. According to logger style this + // may be in unicode and may contain ANSI escape codes for colors. + ADDAPI const char* ADDCALL sass_error_get_formatted(const struct SassError* error); + + // Getter for line position where error occurred (starts from 1). + ADDAPI size_t ADDCALL sass_error_get_line(const struct SassError* error); + + // Getter for column position where error occurred (starts from 1). + ADDAPI size_t ADDCALL sass_error_get_column(const struct SassError* error); + + // Getter for source content referenced in line and column. + ADDAPI const char* ADDCALL sass_error_get_content(const struct SassError* error); + + // Getter for path where the error occurred. + ADDAPI const char* ADDCALL sass_error_get_path(const struct SassError* error); + + // Getter for number of traces attached to error object. + ADDAPI size_t ADDCALL sass_error_count_traces(const struct SassError* error); + + // Getter for last trace (or nullptr if none are available). + ADDAPI const struct SassTrace* ADDCALL sass_error_last_trace(const struct SassError* error); + + // Getter for nth trace (or nullptr if `n` is invalid). + ADDAPI const struct SassTrace* ADDCALL sass_error_get_trace(const struct SassError* error, size_t n); + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + +#ifdef __cplusplus +} // __cplusplus defined. +#endif + +#endif diff --git a/include/sass/function.h b/include/sass/function.h new file mode 100644 index 0000000000..aea052bca8 --- /dev/null +++ b/include/sass/function.h @@ -0,0 +1,49 @@ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#ifndef SASS_FUNCTION_H +#define SASS_FUNCTION_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // Type definition for custom functions + typedef struct SassValue* (*SassFunctionLambda)( + struct SassValue*, struct SassCompiler* compiler, void* cookie); + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // Create custom function (with arbitrary data pointer called `cookie`) + // The pointer is often used to store the callback into the actual binding. + ADDAPI struct SassFunction* ADDCALL sass_make_function (const char* signature, SassFunctionLambda lambda, void* cookie); + + // Deallocate custom function and release memory + ADDAPI void ADDCALL sass_delete_function (struct SassFunction* entry); + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // Getter for custom function signature. + ADDAPI const char* ADDCALL sass_function_get_signature (struct SassFunction* function); + + // Getter for custom function lambda. + ADDAPI SassFunctionLambda ADDCALL sass_function_get_lambda (struct SassFunction* function); + + // Getter for custom function data cookie. + ADDAPI void* ADDCALL sass_function_get_cookie (struct SassFunction* function); + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + +#ifdef __cplusplus +} // __cplusplus defined. +#endif + +#endif diff --git a/include/sass/functions.h b/include/sass/functions.h deleted file mode 100644 index ac47e8ede1..0000000000 --- a/include/sass/functions.h +++ /dev/null @@ -1,139 +0,0 @@ -#ifndef SASS_C_FUNCTIONS_H -#define SASS_C_FUNCTIONS_H - -#include -#include -#include - -#ifdef __cplusplus -extern "C" { -#endif - - -// Forward declaration -struct Sass_Env; -struct Sass_Callee; -struct Sass_Import; -struct Sass_Options; -struct Sass_Compiler; -struct Sass_Importer; -struct Sass_Function; - -// Typedef helpers for callee lists -typedef struct Sass_Env (*Sass_Env_Frame); -// Typedef helpers for callee lists -typedef struct Sass_Callee (*Sass_Callee_Entry); -// Typedef helpers for import lists -typedef struct Sass_Import (*Sass_Import_Entry); -typedef struct Sass_Import* (*Sass_Import_List); -// Typedef helpers for custom importer lists -typedef struct Sass_Importer (*Sass_Importer_Entry); -typedef struct Sass_Importer* (*Sass_Importer_List); -// Typedef defining importer signature and return type -typedef Sass_Import_List (*Sass_Importer_Fn) - (const char* url, Sass_Importer_Entry cb, struct Sass_Compiler* compiler); - -// Typedef helpers for custom functions lists -typedef struct Sass_Function (*Sass_Function_Entry); -typedef struct Sass_Function* (*Sass_Function_List); -// Typedef defining function signature and return type -typedef union Sass_Value* (*Sass_Function_Fn) - (const union Sass_Value*, Sass_Function_Entry cb, struct Sass_Compiler* compiler); - -// Type of function calls -enum Sass_Callee_Type { - SASS_CALLEE_MIXIN, - SASS_CALLEE_FUNCTION, - SASS_CALLEE_C_FUNCTION, -}; - -// Creator for sass custom importer return argument list -ADDAPI Sass_Importer_List ADDCALL sass_make_importer_list (size_t length); -ADDAPI Sass_Importer_Entry ADDCALL sass_importer_get_list_entry (Sass_Importer_List list, size_t idx); -ADDAPI void ADDCALL sass_importer_set_list_entry (Sass_Importer_List list, size_t idx, Sass_Importer_Entry entry); -ADDAPI void ADDCALL sass_delete_importer_list (Sass_Importer_List list); - - -// Creators for custom importer callback (with some additional pointer) -// The pointer is mostly used to store the callback into the actual binding -ADDAPI Sass_Importer_Entry ADDCALL sass_make_importer (Sass_Importer_Fn importer, double priority, void* cookie); - -// Getters for import function descriptors -ADDAPI Sass_Importer_Fn ADDCALL sass_importer_get_function (Sass_Importer_Entry cb); -ADDAPI double ADDCALL sass_importer_get_priority (Sass_Importer_Entry cb); -ADDAPI void* ADDCALL sass_importer_get_cookie (Sass_Importer_Entry cb); - -// Deallocator for associated memory -ADDAPI void ADDCALL sass_delete_importer (Sass_Importer_Entry cb); - -// Creator for sass custom importer return argument list -ADDAPI Sass_Import_List ADDCALL sass_make_import_list (size_t length); -// Creator for a single import entry returned by the custom importer inside the list -ADDAPI Sass_Import_Entry ADDCALL sass_make_import_entry (const char* path, char* source, char* srcmap); -ADDAPI Sass_Import_Entry ADDCALL sass_make_import (const char* imp_path, const char* abs_base, char* source, char* srcmap); -// set error message to abort import and to print out a message (path from existing object is used in output) -ADDAPI Sass_Import_Entry ADDCALL sass_import_set_error(Sass_Import_Entry import, const char* message, size_t line, size_t col); - -// Setters to insert an entry into the import list (you may also use [] access directly) -// Since we are dealing with pointers they should have a guaranteed and fixed size -ADDAPI void ADDCALL sass_import_set_list_entry (Sass_Import_List list, size_t idx, Sass_Import_Entry entry); -ADDAPI Sass_Import_Entry ADDCALL sass_import_get_list_entry (Sass_Import_List list, size_t idx); - -// Getters for callee entry -ADDAPI const char* ADDCALL sass_callee_get_name (Sass_Callee_Entry); -ADDAPI const char* ADDCALL sass_callee_get_path (Sass_Callee_Entry); -ADDAPI size_t ADDCALL sass_callee_get_line (Sass_Callee_Entry); -ADDAPI size_t ADDCALL sass_callee_get_column (Sass_Callee_Entry); -ADDAPI enum Sass_Callee_Type ADDCALL sass_callee_get_type (Sass_Callee_Entry); -ADDAPI Sass_Env_Frame ADDCALL sass_callee_get_env (Sass_Callee_Entry); - -// Getters and Setters for environments (lexical, local and global) -ADDAPI union Sass_Value* ADDCALL sass_env_get_lexical (Sass_Env_Frame, const char*); -ADDAPI void ADDCALL sass_env_set_lexical (Sass_Env_Frame, const char*, union Sass_Value*); -ADDAPI union Sass_Value* ADDCALL sass_env_get_local (Sass_Env_Frame, const char*); -ADDAPI void ADDCALL sass_env_set_local (Sass_Env_Frame, const char*, union Sass_Value*); -ADDAPI union Sass_Value* ADDCALL sass_env_get_global (Sass_Env_Frame, const char*); -ADDAPI void ADDCALL sass_env_set_global (Sass_Env_Frame, const char*, union Sass_Value*); - -// Getters for import entry -ADDAPI const char* ADDCALL sass_import_get_imp_path (Sass_Import_Entry); -ADDAPI const char* ADDCALL sass_import_get_abs_path (Sass_Import_Entry); -ADDAPI const char* ADDCALL sass_import_get_source (Sass_Import_Entry); -ADDAPI const char* ADDCALL sass_import_get_srcmap (Sass_Import_Entry); -// Explicit functions to take ownership of these items -// The property on our struct will be reset to NULL -ADDAPI char* ADDCALL sass_import_take_source (Sass_Import_Entry); -ADDAPI char* ADDCALL sass_import_take_srcmap (Sass_Import_Entry); -// Getters from import error entry -ADDAPI size_t ADDCALL sass_import_get_error_line (Sass_Import_Entry); -ADDAPI size_t ADDCALL sass_import_get_error_column (Sass_Import_Entry); -ADDAPI const char* ADDCALL sass_import_get_error_message (Sass_Import_Entry); - -// Deallocator for associated memory (incl. entries) -ADDAPI void ADDCALL sass_delete_import_list (Sass_Import_List); -// Just in case we have some stray import structs -ADDAPI void ADDCALL sass_delete_import (Sass_Import_Entry); - - - -// Creators for sass function list and function descriptors -ADDAPI Sass_Function_List ADDCALL sass_make_function_list (size_t length); -ADDAPI Sass_Function_Entry ADDCALL sass_make_function (const char* signature, Sass_Function_Fn cb, void* cookie); -ADDAPI void ADDCALL sass_delete_function (Sass_Function_Entry entry); -ADDAPI void ADDCALL sass_delete_function_list (Sass_Function_List list); - -// Setters and getters for callbacks on function lists -ADDAPI Sass_Function_Entry ADDCALL sass_function_get_list_entry(Sass_Function_List list, size_t pos); -ADDAPI void ADDCALL sass_function_set_list_entry(Sass_Function_List list, size_t pos, Sass_Function_Entry cb); - -// Getters for custom function descriptors -ADDAPI const char* ADDCALL sass_function_get_signature (Sass_Function_Entry cb); -ADDAPI Sass_Function_Fn ADDCALL sass_function_get_function (Sass_Function_Entry cb); -ADDAPI void* ADDCALL sass_function_get_cookie (Sass_Function_Entry cb); - - -#ifdef __cplusplus -} // __cplusplus defined. -#endif - -#endif diff --git a/include/sass/fwdecl.h b/include/sass/fwdecl.h new file mode 100644 index 0000000000..42e0c6b2f6 --- /dev/null +++ b/include/sass/fwdecl.h @@ -0,0 +1,32 @@ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#ifndef SASS_FWDECL_H +#define SASS_FWDECL_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + + // Forward declare anonymous structs + // C never sees any implementation + struct SassValue; // ref-counted + struct SassTrace; // C++ wrapper + struct SassError; // CAPI-struct + struct SassCompiler; // C++ wrapper + struct SassFunction; // CAPI-struct + struct SassSource; // C++ wrapper + struct SassSrcSpan; // C++ wrapper + struct SassImport; // ref-counted + struct SassImporter; // CAPI-struct + struct SassImportList; // std::deque + struct SassMapIterator; // CAPI-struct + struct SassGetOpt; // Helper + +#ifdef __cplusplus +} // __cplusplus defined. +#endif + +#endif diff --git a/include/sass/getopt.h b/include/sass/getopt.h new file mode 100644 index 0000000000..d7a000d8cb --- /dev/null +++ b/include/sass/getopt.h @@ -0,0 +1,92 @@ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#ifndef SASS_C_GETOPT_H +#define SASS_C_GETOPT_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // Just forward declare + struct SassGetOptEnum; + + // Struct must be known in order to access it in the callback + // We don't expect this to change much once it is proven good + // We also don't want to support all cases under the sun! + union SassOptionValue { + int integer; + bool boolean; + const char* string; + enum SassOutputStyle style; + enum SassImportSyntax syntax; + enum SassSrcMapMode mode; + }; + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // Create a new option parser to help with parsing config from users + // Optimized to act like GNU GetOpt Long to consume main `argv` items + // But can also be used to parse any other list of config strings + ADDAPI struct SassGetOpt* ADDCALL sass_make_getopt(struct SassCompiler* compiler); + + // Delete and finalize the option parser. Make sure to call + // this before you want to start the actual compilation phase. + ADDAPI void ADDCALL sass_delete_getopt(struct SassGetOpt* getopt); + + // Utility function to tell LibSass to register its default options + // It is recommended to always call this function right after creation + ADDAPI void ADDCALL sass_getopt_populate_options(struct SassGetOpt* getopt); + + // Utility function to tell LibSass to register its default arguments + ADDAPI void ADDCALL sass_getopt_populate_arguments(struct SassGetOpt* getopt); + + // Parse one config string at a time, as you would normally do with main `argv`. + ADDAPI void ADDCALL sass_getopt_parse(struct SassGetOpt* getopt, const char* arg); + + // Return string with the full help message describing all commands + // This is formatted in a similar fashion as GNU tools using getopt + ADDAPI char* ADDCALL sass_getopt_get_help(struct SassGetOpt* getopt); + + // Register additional option that can be parsed + ADDAPI void ADDCALL sass_getopt_register_option(struct SassGetOpt* getopt, + // Short and long parameter names + const char short_name, + const char* long_name, + // Description used in help/usage message + const char* description, + // Whether to act like a boolean + const bool boolean, + // Name of required argument + const char* argument, + // Make argument optional + const bool optional, + // Arguments must be one of this enum + const struct SassGetOptEnum* enums, + // Callback function, where we pass back the given option value + void (*cb) (struct SassGetOpt* getopt, union SassOptionValue value)); + + // Register additional argument that can be parsed + ADDAPI void ADDCALL sass_getopt_register_argument(struct SassGetOpt* getopt, + // Whether this argument is optional + bool optional, + // Name used in messages + const char* name, + // Callback function, where we pass back the given argument value + void (*cb) (struct SassGetOpt* getopt, const char* value)); + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + +#ifdef __cplusplus +} // __cplusplus defined. +#endif + +#endif diff --git a/include/sass/import.h b/include/sass/import.h new file mode 100644 index 0000000000..01ea298ced --- /dev/null +++ b/include/sass/import.h @@ -0,0 +1,89 @@ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#ifndef SASS_C_IMPORT_H +#define SASS_C_IMPORT_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // Create import entry by reading from `stdin`. + ADDAPI struct SassImport* ADDCALL sass_make_stdin_import(const char* imp_path); + + // Create import entry to load the passed input path. + ADDAPI struct SassImport* ADDCALL sass_make_file_import(const char* imp_path); + + // Create import entry for the passed data with optional path. + // Note: we take ownership of the passed `content` memory. + ADDAPI struct SassImport* ADDCALL sass_make_content_import(char* content, const char* imp_path); + + // Create single import entry returned by the custom importer inside the list. + // Note: source/srcmap can be empty to let LibSass do the file resolving. + // Note: we take ownership of the passed `source` and `srcmap` memory. + ADDAPI struct SassImport* ADDCALL sass_make_import(const char* imp_path, const char* abs_base, + char* source, char* srcmap, enum SassImportSyntax format); + + // Just in case we have some stray import structs + ADDAPI void ADDCALL sass_delete_import(struct SassImport* import); + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // Getter for specific import format for the given import (force css/sass/scss or set to auto) + ADDAPI enum SassImportSyntax ADDCALL sass_import_get_type(const struct SassImport* import); + + // Setter for specific import format for the given import (force css/sass/scss or set to auto) + ADDAPI void ADDCALL sass_import_set_syntax(struct SassImport* import, enum SassImportSyntax syntax); + + // Getter for original import path (as seen when parsed) + ADDAPI const char* ADDCALL sass_import_get_imp_path(const struct SassImport* import); + + // Getter for resolve absolute path (after being resolved) + ADDAPI const char* ADDCALL sass_import_get_abs_path(const struct SassImport* import); + + // Getter for import error message (used by custom importers). + // If error is not `nullptr`, the import must be considered as failed. + ADDAPI const char* ADDCALL sass_import_get_error_message(struct SassImport* import); + + // Setter for import error message (used by custom importers). + // If error is not `nullptr`, the import must be considered as failed. + ADDAPI void ADDCALL sass_import_set_error_message(struct SassImport* import, const char* msg); + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // Create new list container for imports. + ADDAPI struct SassImportList* ADDCALL sass_make_import_list(); + + // Release memory of list container and all children. + ADDAPI void ADDCALL sass_delete_import_list(struct SassImportList* list); + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // Return number of items currently in the list. + ADDAPI size_t ADDCALL sass_import_list_size(struct SassImportList* list); + + // Remove and return first item in the list (as in a fifo queue). + ADDAPI struct SassImport* ADDCALL sass_import_list_shift(struct SassImportList* list); + + // Append additional import to the list container. + ADDAPI void ADDCALL sass_import_list_push(struct SassImportList* list, struct SassImport* import); + + // Append additional import to the list container and takes ownership of the import. + ADDAPI void ADDCALL sass_import_list_emplace(struct SassImportList* list, struct SassImport* import); + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + +#ifdef __cplusplus +} // __cplusplus defined. +#endif + +#endif diff --git a/include/sass/importer.h b/include/sass/importer.h new file mode 100644 index 0000000000..2f49120fb9 --- /dev/null +++ b/include/sass/importer.h @@ -0,0 +1,51 @@ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#ifndef SASS_C_IMPORTER_H +#define SASS_C_IMPORTER_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // Type definitions for importer functions + typedef struct SassImportList* (*SassImporterLambda)( + const char* url, struct SassImporter* cb, struct SassCompiler* compiler); + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // Create custom importer (with arbitrary data pointer called `cookie`) + // The pointer is often used to store the callback into the actual binding. + ADDAPI struct SassImporter* ADDCALL sass_make_importer( + SassImporterLambda lambda, double priority, void* cookie); + + // Deallocate the importer and release memory + ADDAPI void ADDCALL sass_delete_importer(struct SassImporter* cb); + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // Getter for importer lambda function (the one being actually invoked) + ADDAPI SassImporterLambda ADDCALL sass_importer_get_lambda(struct SassImporter* cb); + + // Getter for importer priority (lowest priority is invoked first) + ADDAPI double ADDCALL sass_importer_get_priority(struct SassImporter* cb); + + // Getter for arbitrary cookie (used by implementers to store stuff) + ADDAPI void* ADDCALL sass_importer_get_cookie(struct SassImporter* cb); + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + +#ifdef __cplusplus +} // __cplusplus defined. +#endif + +#endif diff --git a/include/sass/traces.h b/include/sass/traces.h new file mode 100644 index 0000000000..8949aff578 --- /dev/null +++ b/include/sass/traces.h @@ -0,0 +1,84 @@ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#ifndef SASS_TRACES_H +#define SASS_TRACES_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + + // Traces must be got directly from the underlying object. We expose + // traces during eval (BackTraces) and when handling error (StackTraces). + + ///////////////////////////////////////////////////////////////////////// + // Implementation related to struct SassTrace + ///////////////////////////////////////////////////////////////////////// + + // Getter for name of this trace (normally the function name or empty). + ADDAPI const char* ADDCALL sass_trace_get_name(struct SassTrace* trace); + + // Getter to check if trace is from a function call (otherwise import). + ADDAPI bool ADDCALL sass_trace_was_fncall(struct SassTrace* trace); + + // Getter for the SourceSpan (aka ParserState) for further details + ADDAPI const struct SassSrcSpan* ADDCALL sass_trace_get_srcspan(struct SassTrace* trace); + + ///////////////////////////////////////////////////////////////////////// + // Implementation related to struct SassSrcSpan + ///////////////////////////////////////////////////////////////////////// + + // Getter for line position of trace (starting from 0) + ADDAPI size_t ADDCALL sass_srcspan_get_src_ln(struct SassSrcSpan* pstate); + + // Getter for column position of trace (starting from 0) + ADDAPI size_t ADDCALL sass_srcspan_get_src_col(struct SassSrcSpan* pstate); + + // Getter for line position of trace (starting from 1) + ADDAPI size_t ADDCALL sass_srcspan_get_src_line(struct SassSrcSpan* pstate); + + // Getter for column position of trace (starting from 1) + ADDAPI size_t ADDCALL sass_srcspan_get_src_column(struct SassSrcSpan* pstate); + + // Getter for line span of trace (starting from 0) + ADDAPI size_t ADDCALL sass_srcspan_get_span_ln(struct SassSrcSpan* pstate); + + // Getter for column span of trace (starting from 0) + ADDAPI size_t ADDCALL sass_srcspan_get_span_col(struct SassSrcSpan* pstate); + + // Getter for attached source of trace for further details + ADDAPI struct SassSource* ADDCALL sass_srcspan_get_source(struct SassSrcSpan* pstate); + + ///////////////////////////////////////////////////////////////////////// + // Implementation related to struct SassSource + ///////////////////////////////////////////////////////////////////////// + + // Getter for absolute path this source was loaded from. This path should + // always be absolute but there is no real hard requirement for it. Custom + // importers may use different pattern for paths. LibSass tries to support + // regular win/nix paths and urls. But we it also try to be agnostic here, + // so anything a custom importer returns will be returned here. + ADDAPI const char* ADDCALL sass_source_get_abs_path(struct SassSource* source); + + // Getter for import path this source was loaded from. This path should + // be as it was found when the import was parsed. This is merely useful + // for debugging purposes, but we keep it around anyway. + ADDAPI const char* ADDCALL sass_source_get_imp_path(struct SassSource* source); + + // Getter for the loaded content attached to the source. + ADDAPI const char* ADDCALL sass_source_get_content(struct SassSource* source); + + // Getter for the loaded srcmap attached to the source. + // Note: not used yet, only here for future improvements. + ADDAPI const char* ADDCALL sass_source_get_srcmap(struct SassSource* source); + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + +#ifdef __cplusplus +} // __cplusplus defined. +#endif + +#endif diff --git a/include/sass/values.h b/include/sass/values.h index 9832038b71..feafc5a508 100644 --- a/include/sass/values.h +++ b/include/sass/values.h @@ -1,142 +1,187 @@ -#ifndef SASS_C_VALUES_H -#define SASS_C_VALUES_H +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#ifndef SASS_VALUES_H +#define SASS_VALUES_H -#include -#include #include +// Implementation Notes: While I was refactoring for LibSass 4.0, I figured we should get +// rid of all intermediate structs that we created when converting back and forth from C +// to CPP. I researched several approaches and will document my findings here. C only knows +// about structs, therefore we can export any struct-ptr from C++ directly to C. This would +// have been the most desirable approach, since any class in C++ can be turned into a struct +// but there are limitations. Mainly we cannot export a name-spaced struct, so we would need to +// define all our "classes" on the root namespace. The main benefit would be that we could +// inspect objects during debugging if linking statically. Another approach would be to wrap +// a ValueObj inside a struct. My final conclusion was to simply create an "anonymous" struct +// on the C-API side, which has no implementation at all. In the actual implementation we +// just trust the pointer to be of the type it should be, or you get undefined behavior. +// Since underlying pointers are often RefCounted, we know how to handle the reference count +// for memory management when destruction is requested from C-API. Whenever the created value +// is a e.g. added to a container, the actual destruction of the original is skipped. + +// ToDo: how should we handle sass colors now that we have RGBA, HSLA and HWBA format? + #ifdef __cplusplus extern "C" { #endif - -// Forward declaration -union Sass_Value; - -// Type for Sass values -enum Sass_Tag { - SASS_BOOLEAN, - SASS_NUMBER, - SASS_COLOR, - SASS_STRING, - SASS_LIST, - SASS_MAP, - SASS_NULL, - SASS_ERROR, - SASS_WARNING -}; - -// Tags for denoting Sass list separators -enum Sass_Separator { - SASS_COMMA, - SASS_SPACE, - // only used internally to represent a hash map before evaluation - // otherwise we would be too early to check for duplicate keys - SASS_HASH -}; - -// Value Operators -enum Sass_OP { - AND, OR, // logical connectives - EQ, NEQ, GT, GTE, LT, LTE, // arithmetic relations - ADD, SUB, MUL, DIV, MOD, // arithmetic functions - NUM_OPS // so we know how big to make the op table -}; - -// Creator functions for all value types -ADDAPI union Sass_Value* ADDCALL sass_make_null (void); -ADDAPI union Sass_Value* ADDCALL sass_make_boolean (bool val); -ADDAPI union Sass_Value* ADDCALL sass_make_string (const char* val); -ADDAPI union Sass_Value* ADDCALL sass_make_qstring (const char* val); -ADDAPI union Sass_Value* ADDCALL sass_make_number (double val, const char* unit); -ADDAPI union Sass_Value* ADDCALL sass_make_color (double r, double g, double b, double a); -ADDAPI union Sass_Value* ADDCALL sass_make_list (size_t len, enum Sass_Separator sep, bool is_bracketed); -ADDAPI union Sass_Value* ADDCALL sass_make_map (size_t len); -ADDAPI union Sass_Value* ADDCALL sass_make_error (const char* msg); -ADDAPI union Sass_Value* ADDCALL sass_make_warning (const char* msg); - -// Generic destructor function for all types -// Will release memory of all associated Sass_Values -// Means we will delete recursively for lists and maps -ADDAPI void ADDCALL sass_delete_value (union Sass_Value* val); - -// Make a deep cloned copy of the given sass value -ADDAPI union Sass_Value* ADDCALL sass_clone_value (const union Sass_Value* val); - -// Execute an operation for two Sass_Values and return the result as a Sass_Value too -ADDAPI union Sass_Value* ADDCALL sass_value_op (enum Sass_OP op, const union Sass_Value* a, const union Sass_Value* b); - -// Stringify a Sass_Values and also return the result as a Sass_Value (of type STRING) -ADDAPI union Sass_Value* ADDCALL sass_value_stringify (const union Sass_Value* a, bool compressed, int precision); - -// Return the sass tag for a generic sass value -// Check is needed before accessing specific values! -ADDAPI enum Sass_Tag ADDCALL sass_value_get_tag (const union Sass_Value* v); - -// Check value to be of a specific type -// Can also be used before accessing properties! -ADDAPI bool ADDCALL sass_value_is_null (const union Sass_Value* v); -ADDAPI bool ADDCALL sass_value_is_number (const union Sass_Value* v); -ADDAPI bool ADDCALL sass_value_is_string (const union Sass_Value* v); -ADDAPI bool ADDCALL sass_value_is_boolean (const union Sass_Value* v); -ADDAPI bool ADDCALL sass_value_is_color (const union Sass_Value* v); -ADDAPI bool ADDCALL sass_value_is_list (const union Sass_Value* v); -ADDAPI bool ADDCALL sass_value_is_map (const union Sass_Value* v); -ADDAPI bool ADDCALL sass_value_is_error (const union Sass_Value* v); -ADDAPI bool ADDCALL sass_value_is_warning (const union Sass_Value* v); - -// Getters and setters for Sass_Number -ADDAPI double ADDCALL sass_number_get_value (const union Sass_Value* v); -ADDAPI void ADDCALL sass_number_set_value (union Sass_Value* v, double value); -ADDAPI const char* ADDCALL sass_number_get_unit (const union Sass_Value* v); -ADDAPI void ADDCALL sass_number_set_unit (union Sass_Value* v, char* unit); - -// Getters and setters for Sass_String -ADDAPI const char* ADDCALL sass_string_get_value (const union Sass_Value* v); -ADDAPI void ADDCALL sass_string_set_value (union Sass_Value* v, char* value); -ADDAPI bool ADDCALL sass_string_is_quoted(const union Sass_Value* v); -ADDAPI void ADDCALL sass_string_set_quoted(union Sass_Value* v, bool quoted); - -// Getters and setters for Sass_Boolean -ADDAPI bool ADDCALL sass_boolean_get_value (const union Sass_Value* v); -ADDAPI void ADDCALL sass_boolean_set_value (union Sass_Value* v, bool value); - -// Getters and setters for Sass_Color -ADDAPI double ADDCALL sass_color_get_r (const union Sass_Value* v); -ADDAPI void ADDCALL sass_color_set_r (union Sass_Value* v, double r); -ADDAPI double ADDCALL sass_color_get_g (const union Sass_Value* v); -ADDAPI void ADDCALL sass_color_set_g (union Sass_Value* v, double g); -ADDAPI double ADDCALL sass_color_get_b (const union Sass_Value* v); -ADDAPI void ADDCALL sass_color_set_b (union Sass_Value* v, double b); -ADDAPI double ADDCALL sass_color_get_a (const union Sass_Value* v); -ADDAPI void ADDCALL sass_color_set_a (union Sass_Value* v, double a); - -// Getter for the number of items in list -ADDAPI size_t ADDCALL sass_list_get_length (const union Sass_Value* v); -// Getters and setters for Sass_List -ADDAPI enum Sass_Separator ADDCALL sass_list_get_separator (const union Sass_Value* v); -ADDAPI void ADDCALL sass_list_set_separator (union Sass_Value* v, enum Sass_Separator value); -ADDAPI bool ADDCALL sass_list_get_is_bracketed (const union Sass_Value* v); -ADDAPI void ADDCALL sass_list_set_is_bracketed (union Sass_Value* v, bool value); -// Getters and setters for Sass_List values -ADDAPI union Sass_Value* ADDCALL sass_list_get_value (const union Sass_Value* v, size_t i); -ADDAPI void ADDCALL sass_list_set_value (union Sass_Value* v, size_t i, union Sass_Value* value); - -// Getter for the number of items in map -ADDAPI size_t ADDCALL sass_map_get_length (const union Sass_Value* v); -// Getters and setters for Sass_Map keys and values -ADDAPI union Sass_Value* ADDCALL sass_map_get_key (const union Sass_Value* v, size_t i); -ADDAPI void ADDCALL sass_map_set_key (union Sass_Value* v, size_t i, union Sass_Value*); -ADDAPI union Sass_Value* ADDCALL sass_map_get_value (const union Sass_Value* v, size_t i); -ADDAPI void ADDCALL sass_map_set_value (union Sass_Value* v, size_t i, union Sass_Value*); - -// Getters and setters for Sass_Error -ADDAPI char* ADDCALL sass_error_get_message (const union Sass_Value* v); -ADDAPI void ADDCALL sass_error_set_message (union Sass_Value* v, char* msg); - -// Getters and setters for Sass_Warning -ADDAPI char* ADDCALL sass_warning_get_message (const union Sass_Value* v); -ADDAPI void ADDCALL sass_warning_set_message (union Sass_Value* v, char* msg); + // Type of Sass values + enum SassValueType { + SASS_BOOLEAN, + SASS_NUMBER, + SASS_COLOR, + SASS_STRING, + SASS_LIST, + SASS_MAP, + SASS_NULL, + SASS_ERROR, + SASS_WARNING, + SASS_FUNCTION, + SASS_CALCULATION, + SASS_CALC_OPERATION, + SASS_MIXIN + }; + + // List separators + enum SassSeparator { + SASS_COMMA, + SASS_SPACE, + SASS_DIV, + // A separator that hasn't yet been determined. + // Singleton lists and empty lists don't have separators defined. This means + // that list functions will prefer other lists' separators if possible. + SASS_UNDEF, + }; + + // Value Operators + enum SassOperator { + OR, AND, // logical connectives + EQ, NEQ, GT, GTE, LT, LTE, // arithmetic relations + ADD, SUB, MUL, DIV, MOD, // arithmetic functions + ASSIGN, IESEQ // special IE single equal + }; + + // Creator functions for all value types + ADDAPI struct SassValue* ADDCALL sass_make_null(void); + ADDAPI struct SassValue* ADDCALL sass_make_boolean(bool val); + ADDAPI struct SassValue* ADDCALL sass_make_string(const char* val, bool is_quoted); + ADDAPI struct SassValue* ADDCALL sass_make_number(double val, const char* unit); + ADDAPI struct SassValue* ADDCALL sass_make_color(double r, double g, double b, double a); + ADDAPI struct SassValue* ADDCALL sass_make_list(enum SassSeparator sep, bool is_bracketed); + ADDAPI struct SassValue* ADDCALL sass_make_map(void); + ADDAPI struct SassValue* ADDCALL sass_make_error(const char* msg); + ADDAPI struct SassValue* ADDCALL sass_make_warning(const char* msg); + + // Generic destructor function for all types + // Will release memory of all associated SassValue children + // Means we will delete recursively for lists and maps + ADDAPI void ADDCALL sass_delete_value(struct SassValue* val); + + // Make a deep cloned copy of the given sass value + ADDAPI struct SassValue* ADDCALL sass_clone_value(struct SassValue* val); + + // Execute an operation for two Sass_Values and return the result as a Sass_Value too + ADDAPI struct SassValue* ADDCALL sass_value_op(enum SassOperator op, struct SassValue* a, struct SassValue* b); + + // Stringify a Sass_Values and also return the result as a Sass_Value (of type STRING) + ADDAPI struct SassValue* ADDCALL sass_value_stringify(struct SassValue* a, bool compressed, int precision); + + // Return the sass tag for a generic sass value + // Check is needed before accessing specific values! + ADDAPI enum SassValueType ADDCALL sass_value_get_tag(struct SassValue* v); + + // Check value to be of a specific type + // Can also be used before accessing properties! + ADDAPI bool ADDCALL sass_value_is_null(struct SassValue* v); + ADDAPI bool ADDCALL sass_value_is_number(struct SassValue* v); + ADDAPI bool ADDCALL sass_value_is_string(struct SassValue* v); + ADDAPI bool ADDCALL sass_value_is_boolean(struct SassValue* v); + ADDAPI bool ADDCALL sass_value_is_color(struct SassValue* v); + ADDAPI bool ADDCALL sass_value_is_list(struct SassValue* v); + ADDAPI bool ADDCALL sass_value_is_map(struct SassValue* v); + ADDAPI bool ADDCALL sass_value_is_error(struct SassValue* v); + ADDAPI bool ADDCALL sass_value_is_warning(struct SassValue* v); + + // Getters and setters for Sass_Number + ADDAPI double ADDCALL sass_number_get_value(struct SassValue* v); + ADDAPI void ADDCALL sass_number_set_value(struct SassValue* v, double value); + ADDAPI const char* ADDCALL sass_number_get_unit(struct SassValue* v); + ADDAPI void ADDCALL sass_number_set_unit(struct SassValue* v, const char* unit); + ADDAPI void ADDCALL sass_number_normalize(struct SassValue* v); // What does it do? + ADDAPI void ADDCALL sass_number_reduce(struct SassValue* v); + + // Getters and setters for Sass_String + ADDAPI const char* ADDCALL sass_string_get_value(struct SassValue* v); + ADDAPI void ADDCALL sass_string_set_value(struct SassValue* v, char* value); + ADDAPI bool ADDCALL sass_string_is_quoted(struct SassValue* v); + ADDAPI void ADDCALL sass_string_set_quoted(struct SassValue* v, bool quoted); + + // Getters and setters for Sass_Boolean + ADDAPI bool ADDCALL sass_boolean_get_value(struct SassValue* v); + ADDAPI void ADDCALL sass_boolean_set_value(struct SassValue* v, bool value); + + // Getters and setters for Sass_Color + ADDAPI double ADDCALL sass_color_get_r(struct SassValue* v); + ADDAPI void ADDCALL sass_color_set_r(struct SassValue* v, double r); + ADDAPI double ADDCALL sass_color_get_g(struct SassValue* v); + ADDAPI void ADDCALL sass_color_set_g(struct SassValue* v, double g); + ADDAPI double ADDCALL sass_color_get_b(struct SassValue* v); + ADDAPI void ADDCALL sass_color_set_b(struct SassValue* v, double b); + ADDAPI double ADDCALL sass_color_get_a(struct SassValue* v); + ADDAPI void ADDCALL sass_color_set_a(struct SassValue* v, double a); + + ADDAPI size_t ADDCALL sass_list_get_size(struct SassValue* list); + ADDAPI void ADDCALL sass_list_push(struct SassValue* list, struct SassValue* value); + ADDAPI struct SassValue* ADDCALL sass_list_at(struct SassValue* list, size_t i); + ADDAPI struct SassValue* ADDCALL sass_list_pop(struct SassValue* list, struct SassValue* value); + ADDAPI struct SassValue* ADDCALL sass_list_shift(struct SassValue* list, struct SassValue* value); + + + + // Getters and setters for Sass_List + ADDAPI enum SassSeparator ADDCALL sass_list_get_separator(struct SassValue* v); + ADDAPI void ADDCALL sass_list_set_separator(struct SassValue* v, enum SassSeparator separator); + ADDAPI bool ADDCALL sass_list_get_is_bracketed(struct SassValue* v); + ADDAPI void ADDCALL sass_list_set_is_bracketed(struct SassValue* v, bool value); + // Getters and setters for Sass_List values + ADDAPI struct SassValue* ADDCALL sass_list_get_value(struct SassValue* v, size_t i); + ADDAPI void ADDCALL sass_list_set_value(struct SassValue* v, size_t i, struct SassValue* value); + + // Getter for the number of items in map + // ADDAPI size_t ADDCALL sass_map_get_size (struct SassValue* v); + + ADDAPI void ADDCALL sass_map_set(struct SassValue* m, struct SassValue* k, struct SassValue* v); + + ADDAPI struct SassMapIterator* ADDCALL sass_map_make_iterator(struct SassValue* map); + ADDAPI void ADDCALL sass_map_delete_iterator(struct SassMapIterator* it); + ADDAPI bool ADDCALL sass_map_iterator_exhausted(struct SassMapIterator* it); + ADDAPI struct SassValue* ADDCALL sass_map_iterator_get_key(struct SassMapIterator* it); + ADDAPI struct SassValue* ADDCALL sass_map_iterator_get_value(struct SassMapIterator* it); + ADDAPI void ADDCALL sass_map_iterator_next(struct SassMapIterator* it); + + // sass_map_get_iterator(); + // sass_map_iterator_next(it); + + + ADDAPI void ADDCALL sass_map_set(struct SassValue* m, struct SassValue* k, struct SassValue* v); + ADDAPI struct SassValue* ADDCALL sass_map_get(struct SassValue* m, struct SassValue* k); + + + // Getters and setters for Sass_Map keys and values + //ADDAPI struct SassValue* ADDCALL sass_map_get_key (struct SassValue* v, size_t i); + //ADDAPI void ADDCALL sass_map_set_key (struct SassValue* v, size_t i, struct SassValue*); + //ADDAPI struct SassValue* ADDCALL sass_map_get_value (struct SassValue* v, size_t i); + //ADDAPI void ADDCALL sass_map_set_value (struct SassValue* v, size_t i, struct SassValue*); + + // Getters and setters for Sass_Error + ADDAPI const char* ADDCALL sass_error_get_message(struct SassValue* v); + ADDAPI void ADDCALL sass_error_set_message(struct SassValue* v, const char* msg); + + // Getters and setters for Sass_Warning + ADDAPI const char* ADDCALL sass_warning_get_message(struct SassValue* v); + ADDAPI void ADDCALL sass_warning_set_message(struct SassValue* v, const char* msg); #ifdef __cplusplus } // __cplusplus defined. diff --git a/include/sass/variable.h b/include/sass/variable.h new file mode 100644 index 0000000000..5fbd925e38 --- /dev/null +++ b/include/sass/variable.h @@ -0,0 +1,41 @@ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#ifndef SASS_VARIABLE_H +#define SASS_VARIABLE_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // Getter for lexical variable (lexical to scope where function is called). + // Note: C-API function can only access existing variables and not create new ones! + ADDAPI struct SassValue* ADDCALL sass_env_get_lexical (struct SassCompiler* compiler, const char* name); + + // Setter for lexical variable (lexical to scope where function is called). + // Returns true if variable was set or false if it does not exist (we can't create it) + // Note: C-API function can only access existing variables and not create new ones! + ADDAPI bool ADDCALL sass_env_set_lexical(struct SassCompiler* compiler, const char* name, struct SassValue* value); + + // Getter for global variable (only variables on the root scope are considered). + // Note: C-API function can only access existing variables and not create new ones! + ADDAPI struct SassValue* ADDCALL sass_env_get_global (struct SassCompiler* compiler, const char* name); + + // Setter for global variable (only variables on the root scope are considered). + // Returns true if variable was set or false if it does not exist (we can't create it) + // Note: C-API function can only access existing variables and not create new ones! + ADDAPI bool ADDCALL sass_env_set_global(struct SassCompiler* compiler, const char* name, struct SassValue* value); + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + +#ifdef __cplusplus +} // EO extern "C". +#endif + +#endif diff --git a/include/sass/version.h b/include/sass/version.h index 56ea016a25..bfa5509aed 100644 --- a/include/sass/version.h +++ b/include/sass/version.h @@ -1,12 +1,18 @@ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ #ifndef SASS_VERSION_H #define SASS_VERSION_H - + +// In order to report something useful this constant must be +// passed during compilation (e.g. -DLIBSASS_VERSION=\"x.y.z\"). +// Note: what you pass must be a valid string with quotes! #ifndef LIBSASS_VERSION #define LIBSASS_VERSION "[NA]" #endif #ifndef LIBSASS_LANGUAGE_VERSION -#define LIBSASS_LANGUAGE_VERSION "3.5" +#define LIBSASS_LANGUAGE_VERSION "3.9" #endif #endif diff --git a/include/sass/version.h.in b/include/sass/version.h.in index b8d4072d4d..8b2d1c81b5 100644 --- a/include/sass/version.h.in +++ b/include/sass/version.h.in @@ -1,12 +1,18 @@ -#ifndef SASS_VERSION_H -#define SASS_VERSION_H +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#ifndef SASS_VERSION_IN_H +#define SASS_VERSION_IN_H +// In order to report something useful this constant must be +// passed during compilation (e.g. -DLIBSASS_VERSION=\"x.y.z\"). +// Note: what you pass must be a valid string with quotes! #ifndef LIBSASS_VERSION #define LIBSASS_VERSION "@PACKAGE_VERSION@" #endif #ifndef LIBSASS_LANGUAGE_VERSION -#define LIBSASS_LANGUAGE_VERSION "3.5" +#define LIBSASS_LANGUAGE_VERSION "3.9" #endif #endif diff --git a/include/sass2scss.h b/include/sass2scss.h deleted file mode 100644 index 8736b2cb9d..0000000000 --- a/include/sass2scss.h +++ /dev/null @@ -1,120 +0,0 @@ -/** - * sass2scss - * Licensed under the MIT License - * Copyright (c) Marcel Greter - */ - -#ifndef SASS2SCSS_H -#define SASS2SCSS_H - -#ifdef _WIN32 - - /* You should define ADD_EXPORTS *only* when building the DLL. */ - #ifdef ADD_EXPORTS - #define ADDAPI __declspec(dllexport) - #define ADDCALL __cdecl - #else - #define ADDAPI - #define ADDCALL - #endif - -#else /* _WIN32 not defined. */ - - /* Define with no value on non-Windows OSes. */ - #define ADDAPI - #define ADDCALL - -#endif - -#ifdef __cplusplus - -#include -#include -#include -#include -#include - -#ifndef SASS2SCSS_VERSION -// Hardcode once the file is copied from -// https://github.com/mgreter/sass2scss -#define SASS2SCSS_VERSION "1.1.1" -#endif - -// add namespace for c++ -namespace Sass -{ - - // pretty print options - const int SASS2SCSS_PRETTIFY_0 = 0; - const int SASS2SCSS_PRETTIFY_1 = 1; - const int SASS2SCSS_PRETTIFY_2 = 2; - const int SASS2SCSS_PRETTIFY_3 = 3; - - // remove one-line comment - const int SASS2SCSS_KEEP_COMMENT = 32; - // remove multi-line comments - const int SASS2SCSS_STRIP_COMMENT = 64; - // convert one-line to multi-line - const int SASS2SCSS_CONVERT_COMMENT = 128; - - // String for finding something interesting - const std::string SASS2SCSS_FIND_WHITESPACE = " \t\n\v\f\r"; - - // converter struct - // holding all states - struct converter - { - // bit options - int options; - // is selector - bool selector; - // concat lists - bool comma; - // has property - bool property; - // has semicolon - bool semicolon; - // comment context - std::string comment; - // flag end of file - bool end_of_file; - // whitespace buffer - std::string whitespace; - // context/block stack - std::stack indents; - }; - - // function only available in c++ code - char* sass2scss (const std::string& sass, const int options); - -} -// EO namespace - -// declare for c -extern "C" { -#endif - - // prettyfy print options - #define SASS2SCSS_PRETTIFY_0 0 - #define SASS2SCSS_PRETTIFY_1 1 - #define SASS2SCSS_PRETTIFY_2 2 - #define SASS2SCSS_PRETTIFY_3 3 - - // keep one-line comments - #define SASS2SCSS_KEEP_COMMENT 32 - // remove multi-line comments - #define SASS2SCSS_STRIP_COMMENT 64 - // convert one-line to multi-line - #define SASS2SCSS_CONVERT_COMMENT 128 - - // available to c and c++ code - ADDAPI char* ADDCALL sass2scss (const char* sass, const int options); - - // Get compiled sass2scss version - ADDAPI const char* ADDCALL sass2scss_version(void); - -#ifdef __cplusplus -} // __cplusplus defined. -#endif - -#endif \ No newline at end of file diff --git a/res/resource.rc b/res/resource.rc index bfbb2c2e09..17289a7117 100644 --- a/res/resource.rc +++ b/res/resource.rc @@ -20,12 +20,12 @@ BEGIN BEGIN VALUE "CompanyName", "Sass Open Source Foundation" VALUE "FileDescription", "A C/C++ implementation of a Sass compiler" - VALUE "FileVersion", "1.0.0.0" + VALUE "FileVersion", "2.0.0.0" VALUE "InternalName", "libsass" - VALUE "LegalCopyright", "\251 2017 sass-lang.org" + VALUE "LegalCopyright", "\251 2021 libsass.org" VALUE "OriginalFilename", "libsass.dll" VALUE "ProductName", "LibSass Library" - VALUE "ProductVersion", "1.0.0.0" + VALUE "ProductVersion", "2.0.0.0" END END BLOCK "VarFileInfo" diff --git a/script/bootstrap b/script/bootstrap index b0df8b2367..cb814484a3 100755 --- a/script/bootstrap +++ b/script/bootstrap @@ -7,11 +7,11 @@ script/branding : ${SASS_SASSC_PATH:="sassc" } if [ ! -d $LIBSASS_SPEC_PATH ]; then - git clone https://github.com/mgreter/libsass-spec.git $LIBSASS_SPEC_PATH + git clone https://github.com/mgreter/libsass-spec.git --branch refactor/libsass-4-alpha $LIBSASS_SPEC_PATH fi if [ ! -d $SASS_SPEC_PATH ]; then - git clone https://github.com/sass/sass-spec.git $SASS_SPEC_PATH + git clone https://github.com/mgreter/sass-spec.git --branch refactor/libsass-4-alpha $SASS_SPEC_PATH fi if [ ! -d $SASS_SASSC_PATH ]; then - git clone https://github.com/sass/sassc.git $SASS_SASSC_PATH + git clone https://github.com/mgreter/sassc.git --branch refactor/libsass-4-alpha $SASS_SASSC_PATH fi diff --git a/script/ci-build-libsass b/script/ci-build-libsass index 84f25e00cb..fbb98d242f 100755 --- a/script/ci-build-libsass +++ b/script/ci-build-libsass @@ -44,15 +44,15 @@ fi if [ "$(expr substr $(uname -s) 1 10)" == "MINGW32_NT" ]; then MAKE_OPTS="$MAKE_OPTS -j1 V=1" else - MAKE_OPTS="$MAKE_OPTS -j5 V=1" + MAKE_OPTS="$MAKE_OPTS -j8 V=1" fi if [ "x$PREFIX" == "x" ]; then - if [ "x$TRAVIS_BUILD_DIR" == "x" ]; then - PREFIX=$SASS_LIBSASS_PATH/build - else - PREFIX=$TRAVIS_BUILD_DIR/build - fi + #if [ "x$TRAVIS_BUILD_DIR" == "x" ]; then + PREFIX=$SASS_LIBSASS_PATH/installed + #else + # PREFIX=$TRAVIS_BUILD_DIR/build + #fi fi # enable address sanitation @@ -60,9 +60,9 @@ fi if [ "x$CC" == "xclang" ]; then if [ "x$COVERAGE" != "xyes" ]; then if [ "$TRAVIS_OS_NAME" == "linux" ]; then - export EXTRA_CFLAGS="$EXTRA_CFLAGS -fsanitize=address" - export EXTRA_CXXFLAGS="$EXTRA_CXXFLAGS -fsanitize=address" - export EXTRA_LDFLAGS="$EXTRA_LDFLAGS -fsanitize=address" + export EXTRA_CFLAGS="$EXTRA_CFLAGS -g -fsanitize=address" + export EXTRA_CXXFLAGS="$EXTRA_CXXFLAGS -g -fsanitize=address" + export EXTRA_LDFLAGS="$EXTRA_LDFLAGS -g -fsanitize=address" fi fi fi @@ -71,13 +71,19 @@ echo SASS_LIBSASS_PATH: $SASS_LIBSASS_PATH echo TRAVIS_BUILD_DIR: $TRAVIS_BUILD_DIR echo SASS_SASSC_PATH: $SASS_SASSC_PATH echo SASS_SPEC_PATH: $SASS_SPEC_PATH +echo EXTRA_CFLAGS: $EXTRA_CFLAGS +echo EXTRA_CXXFLAGS: $EXTRA_CXXFLAGS +echo EXTRA_LDFLAGS: $EXTRA_LDFLAGS +echo MAKE_OPTS: $MAKE_OPTS echo INSTALL_LOCATION: $PREFIX if [ "x$AUTOTOOLS" == "xyes" ]; then echo -en 'travis_fold:start:configure\r' autoreconf --force --install - ./configure --enable-tests $COVERAGE_OPT \ + mkdir -p build + cd build + ../configure --enable-tests $COVERAGE_OPT \ --disable-silent-rules \ --with-sassc-dir=$SASS_SASSC_PATH \ --with-sass-spec-dir=$SASS_SPEC_PATH \ @@ -85,20 +91,29 @@ if [ "x$AUTOTOOLS" == "xyes" ]; then ${SHARED_OPT} echo -en 'travis_fold:end:configure\r' - make $MAKE_OPTS clean + echo "MAKE CLEAN" + + PREFIX="$PREFIX" make $MAKE_OPTS clean + + echo "MAKE INSTALL" + + # install to prefix directory + PREFIX="$PREFIX" make $MAKE_OPTS install + + cd .. else - make $MAKE_OPTS clean + PREFIX="$PREFIX" make $MAKE_OPTS clean # Run C++ unit tests - make $MAKE_OPTS -C test clean - make $MAKE_OPTS -C test test + # make $MAKE_OPTS -C test clean + # make $MAKE_OPTS -C test test -fi + # install to prefix directory + PREFIX="$PREFIX" make $MAKE_OPTS install -# install to prefix directory -PREFIX="$PREFIX" make $MAKE_OPTS install +fi ls -la $PREFIX/* @@ -109,28 +124,42 @@ if [ "$CONTINUOUS_INTEGRATION" == "true" ] && [ "$TRAVIS_PULL_REQUEST" != "false ([ "$TRAVIS_OS_NAME" == "linux" ] || [ "$TRAVIS_OS_NAME" == "osx" ] || [ "$TRAVIS_OS_NAME" == "cygwin" ]); then - echo "Fetching PR $TRAVIS_PULL_REQUEST" + if [ "x$TRAVIS_PULL_REQUEST" != "2918" ]; then - JSON=$(curl -L -sS https://api.github.com/repos/sass/libsass/pulls/$TRAVIS_PULL_REQUEST) + echo "Checking out refactoring branch" + cd sass-spec + if ! git config remote.mgreter.url > /dev/null; then + git remote add mgreter https://github.com/mgreter/sass-spec.git -f + git checkout -b refactoring mgreter/refactor/libsass-4-alpha + fi + cd .. + make $MAKE_OPTS test_probe - if [[ $JSON =~ "API rate limit exceeded" ]]; - then - echo "Travis rate limit on github exceeded" - echo "Retrying via 'special purpose proxy'" - JSON=$(curl -L -sS https://github-api-reverse-proxy.herokuapp.com/repos/sass/libsass/pulls/$TRAVIS_PULL_REQUEST) - fi + else - RE_SPEC_PR="sass\/sass-spec(#|\/pull\/)([0-9]+)" + echo "Fetching PR $TRAVIS_PULL_REQUEST" - if [[ $JSON =~ $RE_SPEC_PR ]]; - then - SPEC_PR="${BASH_REMATCH[2]}" - echo "Fetching Sass Spec PR $SPEC_PR" - git -C sass-spec fetch -u origin pull/$SPEC_PR/head:ci-spec-pr-$SPEC_PR - git -C sass-spec checkout --force ci-spec-pr-$SPEC_PR - make $MAKE_OPTS test_probe - else - make $MAKE_OPTS test_probe + JSON=$(curl -L -sS https://api.github.com/repos/sass/libsass/pulls/$TRAVIS_PULL_REQUEST) + + if [[ $JSON =~ "API rate limit exceeded" ]]; + then + echo "Travis rate limit on github exceeded" + echo "Retrying via 'special purpose proxy'" + JSON=$(curl -L -sS https://github-api-reverse-proxy.herokuapp.com/repos/sass/libsass/pulls/$TRAVIS_PULL_REQUEST) + fi + + RE_SPEC_PR="sass\/sass-spec(#|\/pull\/)([0-9]+)" + + if [[ $JSON =~ $RE_SPEC_PR ]]; + then + SPEC_PR="${BASH_REMATCH[2]}" + echo "Fetching Sass Spec PR $SPEC_PR" + git -C sass-spec fetch -u origin pull/$SPEC_PR/head:ci-spec-pr-$SPEC_PR + git -C sass-spec checkout --force ci-spec-pr-$SPEC_PR + make $MAKE_OPTS test_probe + else + make $MAKE_OPTS test_probe + fi fi else make $MAKE_OPTS test_probe diff --git a/script/ci-build-plugin b/script/ci-build-plugin index 533a3f512e..107015c326 100755 --- a/script/ci-build-plugin +++ b/script/ci-build-plugin @@ -34,11 +34,7 @@ fi mkdir -p plugins if [ ! -d plugins/libsass-${PLUGIN} ] ; then - if [ "$PLUGIN" == "tests" ]; then - git clone https://github.com/mgreter/libsass-${PLUGIN} plugins/libsass-${PLUGIN} --branch master - else - git clone https://github.com/mgreter/libsass-${PLUGIN} plugins/libsass-${PLUGIN} - fi + git clone https://github.com/mgreter/libsass-${PLUGIN} plugins/libsass-${PLUGIN} --branch feature/libsass-4.0 fi if [ ! -d plugins/libsass-${PLUGIN}/build ] ; then mkdir plugins/libsass-${PLUGIN}/build @@ -54,11 +50,11 @@ cd ../../.. # glob only works on paths relative to imports if [ "x$PLUGIN" == "xglob" ]; then - ${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 + ${SASSC_BIN} --precision 10 --plugin-path plugins/libsass-${PLUGIN}/build ${SASS_SPEC_SPEC_DIR}/basic/input.scss > ${SASS_SPEC_SPEC_DIR}/basic/result.css + # ${SASSC_BIN} --precision 10 --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 + # 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 fi RETVAL=$?; if [ "$RETVAL" != "0" ]; then exit $RETVAL; fi diff --git a/script/ci-report-coverage b/script/ci-report-coverage index 495cb05cbd..a5a42ec192 100755 --- a/script/ci-report-coverage +++ b/script/ci-report-coverage @@ -19,11 +19,9 @@ if [ "x$COVERAGE" = "xyes" ]; then --exclude src/cencode.c --exclude src/b64 --exclude src/utf8 - --exclude src/utf8_string.hpp + --exclude src/unicode.hpp --exclude src/utf8.h - --exclude src/utf8_string.cpp - --exclude src/sass2scss.h - --exclude src/sass2scss.cpp + --exclude src/unicode.cpp --exclude src/test --exclude src/posix --exclude src/debugger.hpp" diff --git a/src/GNUmakefile.am b/src/GNUmakefile.am index 9b0e6a99b3..59703e19bc 100644 --- a/src/GNUmakefile.am +++ b/src/GNUmakefile.am @@ -30,7 +30,7 @@ include $(top_srcdir)/Makefile.conf libsass_la_SOURCES = ${CSOURCES} ${SOURCES} -libsass_la_LDFLAGS = $(AM_LDFLAGS) -no-undefined -version-info 1:0:0 +libsass_la_LDFLAGS = $(AM_LDFLAGS) -no-undefined -version-info 2:0:0 if ENABLE_TESTS if ENABLE_COVERAGE @@ -38,13 +38,19 @@ nodist_EXTRA_libsass_la_SOURCES = non-existent-file-to-force-CXX-linking.cxx endif endif -include_HEADERS = $(top_srcdir)/include/sass.h \ - $(top_srcdir)/include/sass2scss.h +include_HEADERS = $(top_srcdir)/include/sass.h sass_includedir = $(includedir)/sass sass_include_HEADERS = $(top_srcdir)/include/sass/base.h \ + $(top_srcdir)/include/sass/compiler.h \ + $(top_srcdir)/include/sass/enums.h \ + $(top_srcdir)/include/sass/error.h \ + $(top_srcdir)/include/sass/function.h \ + $(top_srcdir)/include/sass/fwdecl.h \ + $(top_srcdir)/include/sass/import.h \ + $(top_srcdir)/include/sass/importer.h \ + $(top_srcdir)/include/sass/traces.h \ $(top_srcdir)/include/sass/values.h \ - $(top_srcdir)/include/sass/version.h \ - $(top_srcdir)/include/sass/context.h \ - $(top_srcdir)/include/sass/functions.h + $(top_srcdir)/include/sass/variable.h \ + $(top_srcdir)/include/sass/version.h diff --git a/src/MurmurHash2.hpp b/src/MurmurHash2.hpp index ab9b1634c6..5e70fae12f 100644 --- a/src/MurmurHash2.hpp +++ b/src/MurmurHash2.hpp @@ -1,15 +1,17 @@ -//----------------------------------------------------------------------------- -// MurmurHash2 was written by Austin Appleby, and is placed in the public -// domain. The author hereby disclaims copyright to this source code. -//----------------------------------------------------------------------------- -// LibSass only needs MurmurHash2, so we made this header only -//----------------------------------------------------------------------------- - -#ifndef _MURMURHASH2_H_ -#define _MURMURHASH2_H_ - -//----------------------------------------------------------------------------- +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +/* MurmurHash2 was written by Austin Appleby, and is placed in the public */ +/* domain. The author hereby disclaims copyright to this source code. */ +/*****************************************************************************/ +/* LibSass only needs MurmurHash2, so we made this header only */ +/*****************************************************************************/ +#ifndef SASS_MURMURHASH2_HPP +#define SASS_MURMURHASH2_HPP + +///////////////////////////////////////////////////////////////////////// // Platform-specific functions and macros +///////////////////////////////////////////////////////////////////////// // Microsoft Visual Studio @@ -23,11 +25,12 @@ typedef unsigned __int64 uint64_t; #else // defined(_MSC_VER) -#include +#include #endif // !defined(_MSC_VER) -//----------------------------------------------------------------------------- +///////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////// inline uint32_t MurmurHash2 ( const void * key, int len, uint32_t seed ) { @@ -85,7 +88,8 @@ inline uint32_t MurmurHash2 ( const void * key, int len, uint32_t seed ) return h; } -//----------------------------------------------------------------------------- +///////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////// -#endif // _MURMURHASH2_H_ +#endif diff --git a/src/ast.cpp b/src/ast.cpp index 2c0dd64c94..7dde73c3db 100644 --- a/src/ast.cpp +++ b/src/ast.cpp @@ -1,953 +1,151 @@ -// sass.hpp must go before all system headers to get the -// __EXTENSIONS__ fix on Solaris. -#include "sass.hpp" - +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ #include "ast.hpp" -namespace Sass { - - static Null sass_null(SourceSpan("null")); - - const char* sass_op_to_name(enum Sass_OP op) { - switch (op) { - case AND: return "and"; - case OR: return "or"; - case EQ: return "eq"; - case NEQ: return "neq"; - case GT: return "gt"; - case GTE: return "gte"; - case LT: return "lt"; - case LTE: return "lte"; - case ADD: return "plus"; - case SUB: return "minus"; - case MUL: return "times"; - case DIV: return "div"; - case MOD: return "mod"; - // this is only used internally! - case NUM_OPS: return "[OPS]"; - default: return "invalid"; - } - } - - const char* sass_op_separator(enum Sass_OP op) { - switch (op) { - case AND: return "&&"; - case OR: return "||"; - case EQ: return "=="; - case NEQ: return "!="; - case GT: return ">"; - case GTE: return ">="; - case LT: return "<"; - case LTE: return "<="; - case ADD: return "+"; - case SUB: return "-"; - case MUL: return "*"; - case DIV: return "/"; - case MOD: return "%"; - // this is only used internally! - case NUM_OPS: return "[OPS]"; - default: return "invalid"; - } - } - - ///////////////////////////////////////////////////////////////////////// - ///////////////////////////////////////////////////////////////////////// - - void AST_Node::update_pstate(const SourceSpan& pstate) - { - pstate_.offset += pstate.position - pstate_.position + pstate.offset; - } - - sass::string AST_Node::to_string(Sass_Inspect_Options opt) const - { - Sass_Output_Options out(opt); - Emitter emitter(out); - Inspect i(emitter); - i.in_declaration = true; - // ToDo: inspect should be const - const_cast(this)->perform(&i); - return i.get_buffer(); - } - - sass::string AST_Node::to_css(Sass_Inspect_Options opt) const - { - opt.output_style = TO_CSS; - Sass_Output_Options out(opt); - Emitter emitter(out); - Inspect i(emitter); - i.in_declaration = true; - // ToDo: inspect should be const - const_cast(this)->perform(&i); - return i.get_buffer(); - } - - sass::string AST_Node::to_string() const - { - return to_string({ NESTED, 5 }); - } - - ///////////////////////////////////////////////////////////////////////// - ///////////////////////////////////////////////////////////////////////// - - Statement::Statement(SourceSpan pstate, Type st, size_t t) - : AST_Node(pstate), statement_type_(st), tabs_(t), group_end_(false) - { } - Statement::Statement(const Statement* ptr) - : AST_Node(ptr), - statement_type_(ptr->statement_type_), - tabs_(ptr->tabs_), - group_end_(ptr->group_end_) - { } - - bool Statement::bubbles() - { - return false; - } - - bool Statement::has_content() - { - return statement_type_ == CONTENT; - } - - bool Statement::is_invisible() const - { - return false; - } - - ///////////////////////////////////////////////////////////////////////// - ///////////////////////////////////////////////////////////////////////// +#include "cssize.hpp" +#include "inspect.hpp" +#include "exceptions.hpp" +#include "dart_helpers.hpp" - Block::Block(SourceSpan pstate, size_t s, bool r) - : Statement(pstate), - Vectorized(s), - is_root_(r) - { } - Block::Block(const Block* ptr) - : Statement(ptr), - Vectorized(*ptr), - is_root_(ptr->is_root_) - { } +#include "debugger.hpp" - bool Block::isInvisible() const - { - for (auto& item : elements()) { - if (!item->is_invisible()) return false; - } - return true; - } - - bool Block::has_content() - { - for (size_t i = 0, L = elements().size(); i < L; ++i) { - if (elements()[i]->has_content()) return true; - } - return Statement::has_content(); - } - - ///////////////////////////////////////////////////////////////////////// - ///////////////////////////////////////////////////////////////////////// - - ParentStatement::ParentStatement(SourceSpan pstate, Block_Obj b) - : Statement(pstate), block_(b) - { } - ParentStatement::ParentStatement(const ParentStatement* ptr) - : Statement(ptr), block_(ptr->block_) - { } - - bool ParentStatement::has_content() - { - return (block_ && block_->has_content()) || Statement::has_content(); - } +namespace Sass { ///////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////// - StyleRule::StyleRule(SourceSpan pstate, SelectorListObj s, Block_Obj b) - : ParentStatement(pstate, b), selector_(s), schema_(), is_root_(false) - { statement_type(RULESET); } - StyleRule::StyleRule(const StyleRule* ptr) - : ParentStatement(ptr), - selector_(ptr->selector_), - schema_(ptr->schema_), - is_root_(ptr->is_root_) - { statement_type(RULESET); } - - bool StyleRule::is_invisible() const { - if (const SelectorList * sl = Cast(selector())) { - for (size_t i = 0, L = sl->length(); i < L; i += 1) - if (!(*sl)[i]->isInvisible()) return false; - } - return true; - } + // Needs to be in sync with SassOp enum + uint8_t SassOpPresedence[15] = { + 1, 2, 3, 3, 4, 4, 4, 4, + 5, 5, 6, 6, 6, 9, 255 + }; - ///////////////////////////////////////////////////////////////////////// - ///////////////////////////////////////////////////////////////////////// + // Needs to be in sync with SassOp enum + const char* SassOpName[16] = { + "or", "and", "eq", "neq", "gt", "gte", "lt", "lte", + "plus", "minus", "times", "div", "mod", "seq", "ieseq", "invalid" + }; - Bubble::Bubble(SourceSpan pstate, Statement_Obj n, Statement_Obj g, size_t t) - : Statement(pstate, Statement::BUBBLE, t), node_(n), group_end_(g == nullptr) - { } - Bubble::Bubble(const Bubble* ptr) - : Statement(ptr), - node_(ptr->node_), - group_end_(ptr->group_end_) - { } + // Needs to be in sync with SassOp enum + const char* SassOpOperator[16] = { + "||", "&&", "==", "!=", ">", ">=", "<", "<=", + "+", "-", "*", "/", "%", "=", "=", "invalid" + }; - bool Bubble::bubbles() + // Precedence is used to decide order + // in ExpressionParser::addOperator. + uint8_t sass_op_to_precedence(enum SassOperator op) { - return true; - } - - ///////////////////////////////////////////////////////////////////////// - ///////////////////////////////////////////////////////////////////////// - - Trace::Trace(SourceSpan pstate, sass::string n, Block_Obj b, char type) - : ParentStatement(pstate, b), type_(type), name_(n) - { } - Trace::Trace(const Trace* ptr) - : ParentStatement(ptr), - type_(ptr->type_), - name_(ptr->name_) - { } - - ///////////////////////////////////////////////////////////////////////// - ///////////////////////////////////////////////////////////////////////// - - AtRule::AtRule(SourceSpan pstate, sass::string kwd, SelectorListObj sel, Block_Obj b, ExpressionObj val) - : ParentStatement(pstate, b), keyword_(kwd), selector_(sel), value_(val) // set value manually if needed - { statement_type(DIRECTIVE); } - AtRule::AtRule(const AtRule* ptr) - : ParentStatement(ptr), - keyword_(ptr->keyword_), - selector_(ptr->selector_), - value_(ptr->value_) // set value manually if needed - { statement_type(DIRECTIVE); } - - bool AtRule::bubbles() { return is_keyframes() || is_media(); } - - bool AtRule::is_media() { - return keyword_.compare("@-webkit-media") == 0 || - keyword_.compare("@-moz-media") == 0 || - keyword_.compare("@-o-media") == 0 || - keyword_.compare("@media") == 0; - } - bool AtRule::is_keyframes() { - return keyword_.compare("@-webkit-keyframes") == 0 || - keyword_.compare("@-moz-keyframes") == 0 || - keyword_.compare("@-o-keyframes") == 0 || - keyword_.compare("@keyframes") == 0; + return SassOpPresedence[op]; } - ///////////////////////////////////////////////////////////////////////// - ///////////////////////////////////////////////////////////////////////// - - Keyframe_Rule::Keyframe_Rule(SourceSpan pstate, Block_Obj b) - : ParentStatement(pstate, b), name_() - { statement_type(KEYFRAMERULE); } - Keyframe_Rule::Keyframe_Rule(const Keyframe_Rule* ptr) - : ParentStatement(ptr), name_(ptr->name_) - { statement_type(KEYFRAMERULE); } - - ///////////////////////////////////////////////////////////////////////// - ///////////////////////////////////////////////////////////////////////// - - Declaration::Declaration(SourceSpan pstate, String_Obj prop, ExpressionObj val, bool i, bool c, Block_Obj b) - : ParentStatement(pstate, b), property_(prop), value_(val), is_important_(i), is_custom_property_(c), is_indented_(false) - { statement_type(DECLARATION); } - Declaration::Declaration(const Declaration* ptr) - : ParentStatement(ptr), - property_(ptr->property_), - value_(ptr->value_), - is_important_(ptr->is_important_), - is_custom_property_(ptr->is_custom_property_), - is_indented_(ptr->is_indented_) - { statement_type(DECLARATION); } - - bool Declaration::is_invisible() const + // Get readable name for error messages + const char* sass_op_to_name(enum SassOperator op) { - if (is_custom_property()) return false; - return !(value_ && !Cast(value_)); + return SassOpName[op]; } - ///////////////////////////////////////////////////////////////////////// - ///////////////////////////////////////////////////////////////////////// - - Assignment::Assignment(SourceSpan pstate, sass::string var, ExpressionObj val, bool is_default, bool is_global) - : Statement(pstate), variable_(var), value_(val), is_default_(is_default), is_global_(is_global) - { statement_type(ASSIGNMENT); } - Assignment::Assignment(const Assignment* ptr) - : Statement(ptr), - variable_(ptr->variable_), - value_(ptr->value_), - is_default_(ptr->is_default_), - is_global_(ptr->is_global_) - { statement_type(ASSIGNMENT); } - - ///////////////////////////////////////////////////////////////////////// - ///////////////////////////////////////////////////////////////////////// - - Import::Import(SourceSpan pstate) - : Statement(pstate), - urls_(sass::vector()), - incs_(sass::vector()), - import_queries_() - { statement_type(IMPORT); } - Import::Import(const Import* ptr) - : Statement(ptr), - urls_(ptr->urls_), - incs_(ptr->incs_), - import_queries_(ptr->import_queries_) - { statement_type(IMPORT); } - - sass::vector& Import::incs() { return incs_; } - sass::vector& Import::urls() { return urls_; } - - ///////////////////////////////////////////////////////////////////////// - ///////////////////////////////////////////////////////////////////////// - - Import_Stub::Import_Stub(SourceSpan pstate, Include res) - : Statement(pstate), resource_(res) - { statement_type(IMPORT_STUB); } - Import_Stub::Import_Stub(const Import_Stub* ptr) - : Statement(ptr), resource_(ptr->resource_) - { statement_type(IMPORT_STUB); } - Include Import_Stub::resource() { return resource_; }; - sass::string Import_Stub::imp_path() { return resource_.imp_path; }; - sass::string Import_Stub::abs_path() { return resource_.abs_path; }; - - ///////////////////////////////////////////////////////////////////////// - ///////////////////////////////////////////////////////////////////////// - - WarningRule::WarningRule(SourceSpan pstate, ExpressionObj msg) - : Statement(pstate), message_(msg) - { statement_type(WARNING); } - WarningRule::WarningRule(const WarningRule* ptr) - : Statement(ptr), message_(ptr->message_) - { statement_type(WARNING); } - - ///////////////////////////////////////////////////////////////////////// - ///////////////////////////////////////////////////////////////////////// - - ErrorRule::ErrorRule(SourceSpan pstate, ExpressionObj msg) - : Statement(pstate), message_(msg) - { statement_type(ERROR); } - ErrorRule::ErrorRule(const ErrorRule* ptr) - : Statement(ptr), message_(ptr->message_) - { statement_type(ERROR); } - - ///////////////////////////////////////////////////////////////////////// - ///////////////////////////////////////////////////////////////////////// - - DebugRule::DebugRule(SourceSpan pstate, ExpressionObj val) - : Statement(pstate), value_(val) - { statement_type(DEBUGSTMT); } - DebugRule::DebugRule(const DebugRule* ptr) - : Statement(ptr), value_(ptr->value_) - { statement_type(DEBUGSTMT); } - - ///////////////////////////////////////////////////////////////////////// - ///////////////////////////////////////////////////////////////////////// - - Comment::Comment(SourceSpan pstate, String_Obj txt, bool is_important) - : Statement(pstate), text_(txt), is_important_(is_important) - { statement_type(COMMENT); } - Comment::Comment(const Comment* ptr) - : Statement(ptr), - text_(ptr->text_), - is_important_(ptr->is_important_) - { statement_type(COMMENT); } - - bool Comment::is_invisible() const + // Get readable name for operator (e.g. `==`) + const char* sass_op_separator(enum SassOperator op) { - return false; + return SassOpOperator[op]; } - ///////////////////////////////////////////////////////////////////////// - ///////////////////////////////////////////////////////////////////////// - - If::If(SourceSpan pstate, ExpressionObj pred, Block_Obj con, Block_Obj alt) - : ParentStatement(pstate, con), predicate_(pred), alternative_(alt) - { statement_type(IF); } - If::If(const If* ptr) - : ParentStatement(ptr), - predicate_(ptr->predicate_), - alternative_(ptr->alternative_) - { statement_type(IF); } - - bool If::has_content() + // Get readable name for list operator (e.g. `,`, `/` or ` `) + const char* sass_list_separator(enum SassSeparator op) { - return ParentStatement::has_content() || (alternative_ && alternative_->has_content()); - } - - ///////////////////////////////////////////////////////////////////////// - ///////////////////////////////////////////////////////////////////////// - - ForRule::ForRule(SourceSpan pstate, - sass::string var, ExpressionObj lo, ExpressionObj hi, Block_Obj b, bool inc) - : ParentStatement(pstate, b), - variable_(var), lower_bound_(lo), upper_bound_(hi), is_inclusive_(inc) - { statement_type(FOR); } - ForRule::ForRule(const ForRule* ptr) - : ParentStatement(ptr), - variable_(ptr->variable_), - lower_bound_(ptr->lower_bound_), - upper_bound_(ptr->upper_bound_), - is_inclusive_(ptr->is_inclusive_) - { statement_type(FOR); } - - ///////////////////////////////////////////////////////////////////////// - ///////////////////////////////////////////////////////////////////////// - - EachRule::EachRule(SourceSpan pstate, sass::vector vars, ExpressionObj lst, Block_Obj b) - : ParentStatement(pstate, b), variables_(vars), list_(lst) - { statement_type(EACH); } - EachRule::EachRule(const EachRule* ptr) - : ParentStatement(ptr), variables_(ptr->variables_), list_(ptr->list_) - { statement_type(EACH); } - - ///////////////////////////////////////////////////////////////////////// - ///////////////////////////////////////////////////////////////////////// - - WhileRule::WhileRule(SourceSpan pstate, ExpressionObj pred, Block_Obj b) - : ParentStatement(pstate, b), predicate_(pred) - { statement_type(WHILE); } - WhileRule::WhileRule(const WhileRule* ptr) - : ParentStatement(ptr), predicate_(ptr->predicate_) - { statement_type(WHILE); } - - ///////////////////////////////////////////////////////////////////////// - ///////////////////////////////////////////////////////////////////////// - - Return::Return(SourceSpan pstate, ExpressionObj val) - : Statement(pstate), value_(val) - { statement_type(RETURN); } - Return::Return(const Return* ptr) - : Statement(ptr), value_(ptr->value_) - { statement_type(RETURN); } - - ///////////////////////////////////////////////////////////////////////// - ///////////////////////////////////////////////////////////////////////// - - ExtendRule::ExtendRule(SourceSpan pstate, SelectorListObj s) - : Statement(pstate), isOptional_(false), selector_(s), schema_() - { statement_type(EXTEND); } - ExtendRule::ExtendRule(SourceSpan pstate, Selector_Schema_Obj s) - : Statement(pstate), isOptional_(false), selector_(), schema_(s) - { - 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) - : ParentStatement(ptr), - name_(ptr->name_), - parameters_(ptr->parameters_), - environment_(ptr->environment_), - type_(ptr->type_), - native_function_(ptr->native_function_), - c_function_(ptr->c_function_), - cookie_(ptr->cookie_), - is_overload_stub_(ptr->is_overload_stub_), - signature_(ptr->signature_) - { } - - Definition::Definition(SourceSpan pstate, - sass::string n, - Parameters_Obj params, - Block_Obj b, - Type t) - : ParentStatement(pstate, b), - name_(n), - parameters_(params), - environment_(0), - type_(t), - native_function_(0), - c_function_(0), - cookie_(0), - is_overload_stub_(false), - signature_(0) - { } - - Definition::Definition(SourceSpan pstate, - Signature sig, - sass::string n, - Parameters_Obj params, - Native_Function func_ptr, - bool overload_stub) - : ParentStatement(pstate, {}), - name_(n), - parameters_(params), - environment_(0), - type_(FUNCTION), - native_function_(func_ptr), - c_function_(0), - cookie_(0), - is_overload_stub_(overload_stub), - signature_(sig) - { } - - Definition::Definition(SourceSpan pstate, - Signature sig, - sass::string n, - Parameters_Obj params, - Sass_Function_Entry c_func) - : ParentStatement(pstate, {}), - name_(n), - parameters_(params), - environment_(0), - type_(FUNCTION), - native_function_(0), - c_function_(c_func), - cookie_(sass_function_get_cookie(c_func)), - is_overload_stub_(false), - signature_(sig) - { } - - ///////////////////////////////////////////////////////////////////////// - ///////////////////////////////////////////////////////////////////////// - - Mixin_Call::Mixin_Call(SourceSpan pstate, sass::string n, Arguments_Obj args, Parameters_Obj b_params, Block_Obj b) - : ParentStatement(pstate, b), name_(n), arguments_(args), block_parameters_(b_params) - { } - Mixin_Call::Mixin_Call(const Mixin_Call* ptr) - : ParentStatement(ptr), - name_(ptr->name_), - arguments_(ptr->arguments_), - block_parameters_(ptr->block_parameters_) - { } - - ///////////////////////////////////////////////////////////////////////// - ///////////////////////////////////////////////////////////////////////// - - Content::Content(SourceSpan pstate, Arguments_Obj args) - : Statement(pstate), - arguments_(args) - { statement_type(CONTENT); } - Content::Content(const Content* ptr) - : Statement(ptr), - arguments_(ptr->arguments_) - { statement_type(CONTENT); } - - ///////////////////////////////////////////////////////////////////////// - ///////////////////////////////////////////////////////////////////////// - - Expression::Expression(SourceSpan pstate, bool d, bool e, bool i, Type ct) - : AST_Node(pstate), - is_delayed_(d), - is_expanded_(e), - is_interpolant_(i), - concrete_type_(ct) - { } - - Expression::Expression(const Expression* ptr) - : AST_Node(ptr), - is_delayed_(ptr->is_delayed_), - is_expanded_(ptr->is_expanded_), - is_interpolant_(ptr->is_interpolant_), - concrete_type_(ptr->concrete_type_) - { } - - ///////////////////////////////////////////////////////////////////////// - ///////////////////////////////////////////////////////////////////////// - - Unary_Expression::Unary_Expression(SourceSpan pstate, Type t, ExpressionObj o) - : Expression(pstate), optype_(t), operand_(o), hash_(0) - { } - Unary_Expression::Unary_Expression(const Unary_Expression* ptr) - : Expression(ptr), - optype_(ptr->optype_), - operand_(ptr->operand_), - hash_(ptr->hash_) - { } - const sass::string Unary_Expression::type_name() { - switch (optype_) { - case PLUS: return "plus"; - case MINUS: return "minus"; - case SLASH: return "slash"; - case NOT: return "not"; - default: return "invalid"; - } - } - bool Unary_Expression::operator==(const Expression& rhs) const - { - try - { - const Unary_Expression* m = Cast(&rhs); - if (m == 0) return false; - return type() == m->type() && - *operand() == *m->operand(); - } - catch (std::bad_cast&) - { - return false; + switch (op) { + case SASS_COMMA: return ", "; + case SASS_SPACE: return " "; + case SASS_DIV: return " / "; + default: return ""; } - catch (...) { throw; } - } - size_t Unary_Expression::hash() const - { - if (hash_ == 0) { - hash_ = std::hash()(optype_); - hash_combine(hash_, operand()->hash()); - }; - return hash_; } ///////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////// - Argument::Argument(SourceSpan pstate, ExpressionObj val, sass::string n, bool rest, bool keyword) - : Expression(pstate), value_(val), name_(n), is_rest_argument_(rest), is_keyword_argument_(keyword), hash_(0) + sass::string Selector::inspect(int precision) const { - if (!name_.empty() && is_rest_argument_) { - coreError("variable-length argument may not be passed by name", pstate_); - } - } - Argument::Argument(const Argument* ptr) - : Expression(ptr), - value_(ptr->value_), - name_(ptr->name_), - is_rest_argument_(ptr->is_rest_argument_), - is_keyword_argument_(ptr->is_keyword_argument_), - hash_(ptr->hash_) - { - if (!name_.empty() && is_rest_argument_) { - coreError("variable-length argument may not be passed by name", pstate_); - } + OutputOptions out( + SASS_STYLE_NESTED, + precision); + Inspect i(out); + i.inspect = true; + // Inspect must be const, accept isn't + const_cast(this)->accept(&i); + return i.get_buffer(); } - void Argument::set_delayed(bool delayed) + sass::string Value::inspect(int precision, bool quotes) const { - if (value_) value_->set_delayed(delayed); - is_delayed(delayed); + OutputOptions out( + SASS_STYLE_NESTED, + precision); + Inspect i(out); + i.inspect = true; + i.quotes = quotes; + // Inspect must be const, accept isn't + const_cast(this)->accept(&i); + return i.get_buffer(); } - bool Argument::operator==(const Expression& rhs) const - { - try - { - const Argument* m = Cast(&rhs); - if (!(m && name() == m->name())) return false; - return *value() == *m->value(); - } - catch (std::bad_cast&) - { - return false; - } - catch (...) { throw; } + AstNode* Value::simplify(Logger& logger) { + callStackFrame frame(logger, pstate()); + throw Exception::SassScriptException(logger, pstate(), + "Value " + inspect() + " can't be used in a calculation."); } - size_t Argument::hash() const + sass::string Value::toCss(bool quote) const { - if (hash_ == 0) { - hash_ = std::hash()(name()); - hash_combine(hash_, value()->hash()); - } - return hash_; - } - - ///////////////////////////////////////////////////////////////////////// - ///////////////////////////////////////////////////////////////////////// - - Arguments::Arguments(SourceSpan pstate) - : Expression(pstate), - Vectorized(), - has_named_arguments_(false), - has_rest_argument_(false), - has_keyword_argument_(false) - { } - Arguments::Arguments(const Arguments* ptr) - : Expression(ptr), - Vectorized(*ptr), - has_named_arguments_(ptr->has_named_arguments_), - has_rest_argument_(ptr->has_rest_argument_), - has_keyword_argument_(ptr->has_keyword_argument_) - { } - - void Arguments::set_delayed(bool delayed) - { - for (Argument_Obj arg : elements()) { - if (arg) arg->set_delayed(delayed); - } - is_delayed(delayed); + OutputOptions out( + SASS_STYLE_TO_CSS, + SassDefaultPrecision); + Cssize i(out); + i.quotes = quote; + // Inspect must be const, accept isn't + const_cast(this)->accept(&i); + return i.get_buffer(); } - Argument_Obj Arguments::get_rest_argument() - { - if (this->has_rest_argument()) { - for (Argument_Obj arg : this->elements()) { - if (arg->is_rest_argument()) { - return arg; - } - } - } - return {}; - } + ////////////////////////////////////////////////////////////////////// + ////////////////////////////////////////////////////////////////////// - Argument_Obj Arguments::get_keyword_argument() + // Only used for nth sass function + // Single values act like lists with 1 item + // Doesn't allow overflow of index (throw error) + // Allows negative index but no overflow either + Value* Value::getValueAt(Value* index, Logger& logger) { - if (this->has_keyword_argument()) { - for (Argument_Obj arg : this->elements()) { - if (arg->is_keyword_argument()) { - return arg; - } - } - } - return {}; + // Check out of boundary access + sassIndexToListIndex(index, logger, "n"); + // Return single value + return this; } - void Arguments::adjust_after_pushing(Argument_Obj a) + // Only used for nth sass function + // Doesn't allow overflow of index (throw error) + // Allows negative index but no overflow either + Value* Map::getValueAt(Value* index, Logger& logger) { - if (!a->name().empty()) { - if (has_keyword_argument()) { - coreError("named arguments must precede variable-length argument", a->pstate()); - } - has_named_arguments(true); - } - else if (a->is_rest_argument()) { - if (has_rest_argument()) { - coreError("functions and mixins may only be called with one variable-length argument", a->pstate()); - } - if (has_keyword_argument_) { - coreError("only keyword arguments may follow variable arguments", a->pstate()); - } - has_rest_argument(true); - } - else if (a->is_keyword_argument()) { - if (has_keyword_argument()) { - coreError("functions and mixins may only be called with one keyword argument", a->pstate()); - } - has_keyword_argument(true); - } - else { - if (has_rest_argument()) { - coreError("ordinal arguments must precede variable-length arguments", a->pstate()); - } - if (has_named_arguments()) { - coreError("ordinal arguments must precede named arguments", a->pstate()); - } - } + return getPairAsList(sassIndexToListIndex(index, logger, "n")); } - ///////////////////////////////////////////////////////////////////////// - ///////////////////////////////////////////////////////////////////////// - - Media_Query::Media_Query(SourceSpan pstate, String_Obj t, size_t s, bool n, bool r) - : Expression(pstate), Vectorized(s), - media_type_(t), is_negated_(n), is_restricted_(r) - { } - Media_Query::Media_Query(const Media_Query* ptr) - : Expression(ptr), - Vectorized(*ptr), - media_type_(ptr->media_type_), - is_negated_(ptr->is_negated_), - is_restricted_(ptr->is_restricted_) - { } - - ///////////////////////////////////////////////////////////////////////// - ///////////////////////////////////////////////////////////////////////// - - Media_Query_Expression::Media_Query_Expression(SourceSpan pstate, - ExpressionObj f, ExpressionObj v, bool i) - : Expression(pstate), feature_(f), value_(v), is_interpolated_(i) - { } - Media_Query_Expression::Media_Query_Expression(const Media_Query_Expression* ptr) - : Expression(ptr), - feature_(ptr->feature_), - value_(ptr->value_), - is_interpolated_(ptr->is_interpolated_) - { } - - ///////////////////////////////////////////////////////////////////////// - ///////////////////////////////////////////////////////////////////////// - - At_Root_Query::At_Root_Query(SourceSpan pstate, ExpressionObj f, ExpressionObj v, bool i) - : Expression(pstate), feature_(f), value_(v) - { } - At_Root_Query::At_Root_Query(const At_Root_Query* ptr) - : Expression(ptr), - feature_(ptr->feature_), - value_(ptr->value_) - { } - - bool At_Root_Query::exclude(sass::string str) - { - bool with = feature() && unquote(feature()->to_string()).compare("with") == 0; - List* l = static_cast(value().ptr()); - sass::string v; - - if (with) - { - if (!l || l->length() == 0) return str.compare("rule") != 0; - for (size_t i = 0, L = l->length(); i < L; ++i) - { - v = unquote((*l)[i]->to_string()); - if (v.compare("all") == 0 || v == str) return false; - } - return true; - } - else - { - if (!l || !l->length()) return str.compare("rule") == 0; - for (size_t i = 0, L = l->length(); i < L; ++i) - { - v = unquote((*l)[i]->to_string()); - if (v.compare("all") == 0 || v == str) return true; - } - return false; - } + // Search the position of the given value + size_t List::indexOf(Value* value) { + return Sass::indexOf(elements(), value); } - ///////////////////////////////////////////////////////////////////////// - ///////////////////////////////////////////////////////////////////////// - - AtRootRule::AtRootRule(SourceSpan pstate, Block_Obj b, At_Root_Query_Obj e) - : ParentStatement(pstate, b), expression_(e) - { statement_type(ATROOT); } - AtRootRule::AtRootRule(const AtRootRule* ptr) - : ParentStatement(ptr), expression_(ptr->expression_) - { statement_type(ATROOT); } - - bool AtRootRule::bubbles() { - return true; - } - - bool AtRootRule::exclude_node(Statement_Obj s) { - if (expression() == nullptr) - { - return s->statement_type() == Statement::RULESET; - } - - if (s->statement_type() == Statement::DIRECTIVE) - { - if (AtRuleObj dir = Cast(s)) - { - sass::string keyword(dir->keyword()); - if (keyword.length() > 0) keyword.erase(0, 1); - return expression()->exclude(keyword); - } - } - if (s->statement_type() == Statement::MEDIA) - { - return expression()->exclude("media"); - } - if (s->statement_type() == Statement::RULESET) - { - return expression()->exclude("rule"); - } - if (s->statement_type() == Statement::SUPPORTS) - { - return expression()->exclude("supports"); - } - if (AtRuleObj dir = Cast(s)) - { - if (dir->is_keyframes()) return expression()->exclude("keyframes"); - } - return false; - } - - ///////////////////////////////////////////////////////////////////////// - ///////////////////////////////////////////////////////////////////////// - - Parameter::Parameter(SourceSpan pstate, sass::string n, ExpressionObj def, bool rest) - : AST_Node(pstate), name_(n), default_value_(def), is_rest_parameter_(rest) - { } - Parameter::Parameter(const Parameter* ptr) - : AST_Node(ptr), - name_(ptr->name_), - default_value_(ptr->default_value_), - is_rest_parameter_(ptr->is_rest_parameter_) - { } - - ///////////////////////////////////////////////////////////////////////// - ///////////////////////////////////////////////////////////////////////// - - Parameters::Parameters(SourceSpan pstate) - : AST_Node(pstate), - Vectorized(), - has_optional_parameters_(false), - has_rest_parameter_(false) - { } - Parameters::Parameters(const Parameters* ptr) - : AST_Node(ptr), - Vectorized(*ptr), - has_optional_parameters_(ptr->has_optional_parameters_), - has_rest_parameter_(ptr->has_rest_parameter_) - { } - - void Parameters::adjust_after_pushing(Parameter_Obj p) + // Only used for nth sass function + // Doesn't allow overflow of index (throw error) + // Allows negative index but no overflow either + Value* List::getValueAt(Value* index, Logger& logger) { - 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()); - } - } + return get(sassIndexToListIndex(index, logger, "n")); } - ///////////////////////////////////////////////////////////////////////// - ///////////////////////////////////////////////////////////////////////// - - // If you forget to add a class here you will get - // undefined reference to `vtable for Sass::Class' - - IMPLEMENT_AST_OPERATORS(StyleRule); - IMPLEMENT_AST_OPERATORS(MediaRule); - IMPLEMENT_AST_OPERATORS(CssMediaRule); - IMPLEMENT_AST_OPERATORS(CssMediaQuery); - IMPLEMENT_AST_OPERATORS(Import); - IMPLEMENT_AST_OPERATORS(Import_Stub); - IMPLEMENT_AST_OPERATORS(AtRule); - IMPLEMENT_AST_OPERATORS(AtRootRule); - IMPLEMENT_AST_OPERATORS(WhileRule); - IMPLEMENT_AST_OPERATORS(EachRule); - IMPLEMENT_AST_OPERATORS(ForRule); - IMPLEMENT_AST_OPERATORS(If); - IMPLEMENT_AST_OPERATORS(Mixin_Call); - IMPLEMENT_AST_OPERATORS(ExtendRule); - IMPLEMENT_AST_OPERATORS(Media_Query); - IMPLEMENT_AST_OPERATORS(Media_Query_Expression); - IMPLEMENT_AST_OPERATORS(DebugRule); - IMPLEMENT_AST_OPERATORS(ErrorRule); - IMPLEMENT_AST_OPERATORS(WarningRule); - IMPLEMENT_AST_OPERATORS(Assignment); - IMPLEMENT_AST_OPERATORS(Return); - IMPLEMENT_AST_OPERATORS(At_Root_Query); - IMPLEMENT_AST_OPERATORS(Comment); - IMPLEMENT_AST_OPERATORS(Parameters); - IMPLEMENT_AST_OPERATORS(Parameter); - IMPLEMENT_AST_OPERATORS(Arguments); - IMPLEMENT_AST_OPERATORS(Argument); - IMPLEMENT_AST_OPERATORS(Unary_Expression); - IMPLEMENT_AST_OPERATORS(Block); - IMPLEMENT_AST_OPERATORS(Content); - IMPLEMENT_AST_OPERATORS(Trace); - IMPLEMENT_AST_OPERATORS(Keyframe_Rule); - IMPLEMENT_AST_OPERATORS(Bubble); - IMPLEMENT_AST_OPERATORS(Definition); - IMPLEMENT_AST_OPERATORS(Declaration); - - ///////////////////////////////////////////////////////////////////////// - ///////////////////////////////////////////////////////////////////////// + ////////////////////////////////////////////////////////////////////// + ////////////////////////////////////////////////////////////////////// } diff --git a/src/ast.hpp b/src/ast.hpp index e7dcaf657d..8c599f3218 100644 --- a/src/ast.hpp +++ b/src/ast.hpp @@ -1,1064 +1,25 @@ -#ifndef SASS_AST_H -#define SASS_AST_H - -// sass.hpp must go before all system headers to get the -// __EXTENSIONS__ fix on Solaris. -#include "sass.hpp" - -#include -#include - -#include "sass/base.h" -#include "ast_helpers.hpp" -#include "ast_fwd_decl.hpp" +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#ifndef SASS_AST_HPP +#define SASS_AST_HPP + +// sass.hpp must go before all system headers +// to get the __EXTENSIONS__ fix on Solaris. +#include "capi_sass.hpp" + +#include "ast_css.hpp" +#include "ast_callables.hpp" +#include "ast_containers.hpp" +#include "ast_expressions.hpp" #include "ast_def_macros.hpp" - -#include "file.hpp" -#include "position.hpp" -#include "operation.hpp" -#include "environment.hpp" -#include "fn_utils.hpp" - -namespace Sass { - - // ToDo: where does this fit best? - // We don't share this with C-API? - class Operand { - public: - Operand(Sass_OP operand, bool ws_before = false, bool ws_after = false) - : operand(operand), ws_before(ws_before), ws_after(ws_after) - { } - public: - enum Sass_OP operand; - bool ws_before; - bool ws_after; - }; - - ////////////////////////////////////////////////////////// - // `hash_combine` comes from boost (functional/hash): - // http://www.boost.org/doc/libs/1_35_0/doc/html/hash/combine.html - // Boost Software License - Version 1.0 - // http://www.boost.org/users/license.html - template - void hash_combine (std::size_t& seed, const T& val) - { - seed ^= std::hash()(val) + 0x9e3779b9 - + (seed<<6) + (seed>>2); - } - ////////////////////////////////////////////////////////// - - const char* sass_op_to_name(enum Sass_OP op); - - const char* sass_op_separator(enum Sass_OP op); - - ////////////////////////////////////////////////////////// - // Abstract base class for all abstract syntax tree nodes. - ////////////////////////////////////////////////////////// - class AST_Node : public SharedObj { - ADD_PROPERTY(SourceSpan, pstate) - public: - AST_Node(SourceSpan pstate) - : pstate_(pstate) - { } - AST_Node(const AST_Node* ptr) - : pstate_(ptr->pstate_) - { } - - // allow implicit conversion to string - // needed for by SharedPtr implementation - operator sass::string() { - return to_string(); - } - - // AST_Node(AST_Node& ptr) = delete; - - virtual ~AST_Node() = 0; - virtual size_t hash() const { return 0; } - virtual sass::string inspect() const { return to_string({ INSPECT, 5 }); } - virtual sass::string to_sass() const { return to_string({ TO_SASS, 5 }); } - virtual sass::string to_string(Sass_Inspect_Options opt) const; - virtual sass::string to_css(Sass_Inspect_Options opt) const; - virtual sass::string to_string() const; - virtual void cloneChildren() {}; - // generic find function (not fully implemented yet) - // ToDo: add specific implementations to all children - virtual bool find ( bool (*f)(AST_Node_Obj) ) { return f(this); }; - void update_pstate(const SourceSpan& pstate); - - // Some objects are not meant to be compared - // ToDo: maybe fall-back to pointer comparison? - virtual bool operator== (const AST_Node& rhs) const { - throw std::runtime_error("operator== not implemented"); - } - - // We can give some reasonable implementations by using - // invert operators on the specialized implementations - virtual bool operator!= (const AST_Node& rhs) const { - // Unequal if not equal - return !(*this == rhs); - } - - ATTACH_ABSTRACT_AST_OPERATIONS(AST_Node); - ATTACH_ABSTRACT_CRTP_PERFORM_METHODS() - }; - inline AST_Node::~AST_Node() { } - - ////////////////////////////////////////////////////////////////////// - // define cast template now (need complete type) - ////////////////////////////////////////////////////////////////////// - - template - T* Cast(AST_Node* ptr) { - return ptr && typeid(T) == typeid(*ptr) ? - static_cast(ptr) : NULL; - }; - - template - const T* Cast(const AST_Node* ptr) { - return ptr && typeid(T) == typeid(*ptr) ? - static_cast(ptr) : NULL; - }; - - ////////////////////////////////////////////////////////////////////// - // Abstract base class for expressions. This side of the AST hierarchy - // represents elements in value contexts, which exist primarily to be - // evaluated and returned. - ////////////////////////////////////////////////////////////////////// - class Expression : public AST_Node { - public: - enum Type { - NONE, - BOOLEAN, - NUMBER, - COLOR, - STRING, - LIST, - MAP, - SELECTOR, - NULL_VAL, - FUNCTION_VAL, - C_WARNING, - C_ERROR, - FUNCTION, - VARIABLE, - PARENT, - NUM_TYPES - }; - private: - // expressions in some contexts shouldn't be evaluated - ADD_PROPERTY(bool, is_delayed) - ADD_PROPERTY(bool, is_expanded) - ADD_PROPERTY(bool, is_interpolant) - ADD_PROPERTY(Type, concrete_type) - public: - Expression(SourceSpan pstate, bool d = false, bool e = false, bool i = false, Type ct = NONE); - virtual operator bool() { return true; } - virtual ~Expression() { } - virtual bool is_invisible() const { return false; } - - virtual sass::string type() const { return ""; } - static sass::string type_name() { return ""; } - - virtual bool is_false() { return false; } - // virtual bool is_true() { return !is_false(); } - virtual bool operator< (const Expression& rhs) const { return false; } - virtual bool operator== (const Expression& rhs) const { return false; } - inline bool operator>(const Expression& rhs) const { return rhs < *this; } - inline bool operator!=(const Expression& rhs) const { return !(rhs == *this); } - virtual bool eq(const Expression& rhs) const { return *this == rhs; }; - virtual void set_delayed(bool delayed) { is_delayed(delayed); } - virtual bool has_interpolant() const { return is_interpolant(); } - virtual bool is_left_interpolant() const { return is_interpolant(); } - virtual bool is_right_interpolant() const { return is_interpolant(); } - ATTACH_VIRTUAL_AST_OPERATIONS(Expression); - size_t hash() const override { return 0; } - }; - -} - -///////////////////////////////////////////////////////////////////////////////////// -// Hash method specializations for std::unordered_map to work with Sass::Expression -///////////////////////////////////////////////////////////////////////////////////// - -namespace std { - template<> - struct hash - { - size_t operator()(Sass::ExpressionObj s) const - { - return s->hash(); - } - }; - template<> - struct equal_to - { - bool operator()( Sass::ExpressionObj lhs, Sass::ExpressionObj rhs) const - { - return lhs->hash() == rhs->hash(); - } - }; -} - -namespace Sass { - - ///////////////////////////////////////////////////////////////////////////// - // Mixin class for AST nodes that should behave like vectors. Uses the - // "Template Method" design pattern to allow subclasses to adjust their flags - // when certain objects are pushed. - ///////////////////////////////////////////////////////////////////////////// - template - class Vectorized { - sass::vector elements_; - protected: - mutable size_t hash_; - void reset_hash() { hash_ = 0; } - virtual void adjust_after_pushing(T element) { } - public: - Vectorized(size_t s = 0) : hash_(0) - { elements_.reserve(s); } - Vectorized(sass::vector vec) : - elements_(std::move(vec)), - hash_(0) - {} - virtual ~Vectorized() = 0; - size_t length() const { return elements_.size(); } - bool empty() const { return elements_.empty(); } - void clear() { return elements_.clear(); } - T& last() { return elements_.back(); } - T& first() { return elements_.front(); } - const T& last() const { return elements_.back(); } - const T& first() const { return elements_.front(); } - - bool operator== (const Vectorized& rhs) const { - // Abort early if sizes do not match - if (length() != rhs.length()) return false; - // Otherwise test each node for object equalicy in order - return std::equal(begin(), end(), rhs.begin(), ObjEqualityFn); - } - - bool operator!= (const Vectorized& rhs) const { - return !(*this == rhs); - } - - T& operator[](size_t i) { return elements_[i]; } - virtual const T& at(size_t i) const { return elements_.at(i); } - virtual T& at(size_t i) { return elements_.at(i); } - const T& get(size_t i) const { return elements_[i]; } - const T& operator[](size_t i) const { return elements_[i]; } - - // Implicitly get the sass::vector from our object - // Makes the Vector directly assignable to sass::vector - // You are responsible to make a copy if needed - // Note: since this returns the real object, we can't - // Note: guarantee that the hash will not get out of sync - operator sass::vector&() { return elements_; } - operator const sass::vector&() const { return elements_; } - - // Explicitly request all elements as a real sass::vector - // You are responsible to make a copy if needed - // Note: since this returns the real object, we can't - // Note: guarantee that the hash will not get out of sync - sass::vector& elements() { return elements_; } - const sass::vector& elements() const { return elements_; } - - // Insert all items from compatible vector - void concat(const sass::vector& v) - { - if (!v.empty()) reset_hash(); - elements().insert(end(), v.begin(), v.end()); - } - - // Syntatic sugar for pointers - void concat(const Vectorized* v) - { - if (v != nullptr) { - return concat(*v); - } - } - - // Insert one item on the front - void unshift(T element) - { - reset_hash(); - elements_.insert(begin(), element); - } - - // Remove and return item on the front - // ToDo: handle empty vectors - T shift() { - reset_hash(); - T first = get(0); - elements_.erase(begin()); - return first; - } - - // Insert one item on the back - // ToDo: rename this to push - void append(T element) - { - reset_hash(); - elements_.insert(end(), element); - // ToDo: Mostly used by parameters and arguments - // ToDo: Find a more elegant way to support this - adjust_after_pushing(element); - } - - // Check if an item already exists - // Uses underlying object `operator==` - // E.g. compares the actual objects - bool contains(const T& el) const { - for (const T& rhs : elements_) { - // Test the underlying objects for equality - // A std::find checks for pointer equality - if (ObjEqualityFn(el, rhs)) { - return true; - } - } - return false; - } - - // This might be better implemented as `operator=`? - void elements(sass::vector e) { - reset_hash(); - elements_ = std::move(e); - } - - virtual size_t hash() const - { - if (hash_ == 0) { - for (const T& el : elements_) { - hash_combine(hash_, el->hash()); - } - } - return hash_; - } - - template - typename sass::vector::iterator insert(P position, const V& val) { - reset_hash(); - return elements_.insert(position, val); - } - - typename sass::vector::iterator end() { return elements_.end(); } - typename sass::vector::iterator begin() { return elements_.begin(); } - typename sass::vector::const_iterator end() const { return elements_.end(); } - typename sass::vector::const_iterator begin() const { return elements_.begin(); } - typename sass::vector::iterator erase(typename sass::vector::iterator el) { reset_hash(); return elements_.erase(el); } - typename sass::vector::const_iterator erase(typename sass::vector::const_iterator el) { reset_hash(); return elements_.erase(el); } - - }; - template - inline Vectorized::~Vectorized() { } - - ///////////////////////////////////////////////////////////////////////////// - // Mixin class for AST nodes that should behave like a hash table. Uses an - // extra internally to maintain insertion order for interation. - ///////////////////////////////////////////////////////////////////////////// - template - class Hashed { - private: - std::unordered_map< - K, T, ObjHash, ObjHashEquality - > elements_; - - sass::vector _keys; - sass::vector _values; - protected: - mutable size_t hash_; - K duplicate_key_; - void reset_hash() { hash_ = 0; } - void reset_duplicate_key() { duplicate_key_ = {}; } - virtual void adjust_after_pushing(std::pair p) { } - public: - Hashed(size_t s = 0) - : elements_(), - _keys(), - _values(), - hash_(0), duplicate_key_({}) - { - _keys.reserve(s); - _values.reserve(s); - elements_.reserve(s); - } - virtual ~Hashed(); - size_t length() const { return _keys.size(); } - bool empty() const { return _keys.empty(); } - bool has(K k) const { - return elements_.find(k) != elements_.end(); - } - T at(K k) const { - if (elements_.count(k)) - { - return elements_.at(k); - } - else { return {}; } - } - bool has_duplicate_key() const { return duplicate_key_ != nullptr; } - K get_duplicate_key() const { return duplicate_key_; } - const std::unordered_map< - K, T, ObjHash, ObjHashEquality - >& elements() { return elements_; } - Hashed& operator<<(std::pair p) - { - reset_hash(); - - if (!has(p.first)) { - _keys.push_back(p.first); - _values.push_back(p.second); - } - else if (!duplicate_key_) { - duplicate_key_ = p.first; - } - - elements_[p.first] = p.second; - - adjust_after_pushing(p); - return *this; - } - Hashed& operator+=(Hashed* h) - { - if (length() == 0) { - this->elements_ = h->elements_; - this->_values = h->_values; - this->_keys = h->_keys; - return *this; - } - - for (auto key : h->keys()) { - *this << std::make_pair(key, h->at(key)); - } - - reset_duplicate_key(); - return *this; - } - const std::unordered_map< - K, T, ObjHash, ObjHashEquality - >& pairs() const { return elements_; } - - const sass::vector& keys() const { return _keys; } - const sass::vector& values() const { return _values; } - -// std::unordered_map::iterator end() { return elements_.end(); } -// std::unordered_map::iterator begin() { return elements_.begin(); } -// std::unordered_map::const_iterator end() const { return elements_.end(); } -// std::unordered_map::const_iterator begin() const { return elements_.begin(); } - - }; - template - inline Hashed::~Hashed() { } - - ///////////////////////////////////////////////////////////////////////// - // Abstract base class for statements. This side of the AST hierarchy - // represents elements in expansion contexts, which exist primarily to be - // rewritten and macro-expanded. - ///////////////////////////////////////////////////////////////////////// - class Statement : public AST_Node { - public: - enum Type { - NONE, - RULESET, - MEDIA, - DIRECTIVE, - SUPPORTS, - ATROOT, - BUBBLE, - CONTENT, - KEYFRAMERULE, - DECLARATION, - ASSIGNMENT, - IMPORT_STUB, - IMPORT, - COMMENT, - WARNING, - RETURN, - EXTEND, - ERROR, - DEBUGSTMT, - WHILE, - EACH, - FOR, - IF - }; - private: - ADD_PROPERTY(Type, statement_type) - ADD_PROPERTY(size_t, tabs) - ADD_PROPERTY(bool, group_end) - public: - Statement(SourceSpan pstate, Type st = NONE, size_t t = 0); - virtual ~Statement() = 0; // virtual destructor - // needed for rearranging nested rulesets during CSS emission - virtual bool bubbles(); - virtual bool has_content(); - virtual bool is_invisible() const; - ATTACH_VIRTUAL_AST_OPERATIONS(Statement) - }; - inline Statement::~Statement() { } - - //////////////////////// - // Blocks of statements. - //////////////////////// - class Block final : public Statement, public Vectorized { - ADD_PROPERTY(bool, is_root) - // needed for properly formatted CSS emission - protected: - void adjust_after_pushing(Statement_Obj s) override {} - public: - Block(SourceSpan pstate, size_t s = 0, bool r = false); - bool isInvisible() const; - bool has_content() override; - ATTACH_AST_OPERATIONS(Block) - ATTACH_CRTP_PERFORM_METHODS() - }; - - //////////////////////////////////////////////////////////////////////// - // Abstract base class for statements that contain blocks of statements. - //////////////////////////////////////////////////////////////////////// - class ParentStatement : public Statement { - ADD_PROPERTY(Block_Obj, block) - public: - ParentStatement(SourceSpan pstate, Block_Obj b); - ParentStatement(const ParentStatement* ptr); // copy constructor - virtual ~ParentStatement() = 0; // virtual destructor - virtual bool has_content() override; - }; - inline ParentStatement::~ParentStatement() { } - - ///////////////////////////////////////////////////////////////////////////// - // Rulesets (i.e., sets of styles headed by a selector and containing a block - // of style declarations. - ///////////////////////////////////////////////////////////////////////////// - class StyleRule final : public ParentStatement { - ADD_PROPERTY(SelectorListObj, selector) - ADD_PROPERTY(Selector_Schema_Obj, schema) - ADD_PROPERTY(bool, is_root); - public: - StyleRule(SourceSpan pstate, SelectorListObj s = {}, Block_Obj b = {}); - bool is_invisible() const override; - ATTACH_AST_OPERATIONS(StyleRule) - ATTACH_CRTP_PERFORM_METHODS() - }; - - ///////////////// - // Bubble. - ///////////////// - class Bubble final : public Statement { - ADD_PROPERTY(Statement_Obj, node) - ADD_PROPERTY(bool, group_end) - public: - Bubble(SourceSpan pstate, Statement_Obj n, Statement_Obj g = {}, size_t t = 0); - bool bubbles() override; - ATTACH_AST_OPERATIONS(Bubble) - ATTACH_CRTP_PERFORM_METHODS() - }; - - ///////////////// - // Trace. - ///////////////// - class Trace final : public ParentStatement { - ADD_CONSTREF(char, type) - ADD_CONSTREF(sass::string, name) - public: - Trace(SourceSpan pstate, sass::string n, Block_Obj b = {}, char type = 'm'); - ATTACH_AST_OPERATIONS(Trace) - ATTACH_CRTP_PERFORM_METHODS() - }; - - /////////////////////////////////////////////////////////////////////// - // At-rules -- arbitrary directives beginning with "@" that may have an - // optional statement block. - /////////////////////////////////////////////////////////////////////// - class AtRule final : public ParentStatement { - ADD_CONSTREF(sass::string, keyword) - ADD_PROPERTY(SelectorListObj, selector) - ADD_PROPERTY(ExpressionObj, value) - public: - AtRule(SourceSpan pstate, sass::string kwd, SelectorListObj sel = {}, Block_Obj b = {}, ExpressionObj val = {}); - bool bubbles() override; - bool is_media(); - bool is_keyframes(); - ATTACH_AST_OPERATIONS(AtRule) - ATTACH_CRTP_PERFORM_METHODS() - }; - - /////////////////////////////////////////////////////////////////////// - // Keyframe-rules -- the child blocks of "@keyframes" nodes. - /////////////////////////////////////////////////////////////////////// - class Keyframe_Rule final : public ParentStatement { - // according to css spec, this should be - // = | - ADD_PROPERTY(SelectorListObj, name) - public: - Keyframe_Rule(SourceSpan pstate, Block_Obj b); - ATTACH_AST_OPERATIONS(Keyframe_Rule) - ATTACH_CRTP_PERFORM_METHODS() - }; - - //////////////////////////////////////////////////////////////////////// - // Declarations -- style rules consisting of a property name and values. - //////////////////////////////////////////////////////////////////////// - class Declaration final : public ParentStatement { - ADD_PROPERTY(String_Obj, property) - ADD_PROPERTY(ExpressionObj, value) - ADD_PROPERTY(bool, is_important) - ADD_PROPERTY(bool, is_custom_property) - ADD_PROPERTY(bool, is_indented) - public: - Declaration(SourceSpan pstate, String_Obj prop, ExpressionObj val, bool i = false, bool c = false, Block_Obj b = {}); - bool is_invisible() const override; - ATTACH_AST_OPERATIONS(Declaration) - ATTACH_CRTP_PERFORM_METHODS() - }; - - ///////////////////////////////////// - // Assignments -- variable and value. - ///////////////////////////////////// - class Assignment final : public Statement { - ADD_CONSTREF(sass::string, variable) - ADD_PROPERTY(ExpressionObj, value) - ADD_PROPERTY(bool, is_default) - ADD_PROPERTY(bool, is_global) - public: - Assignment(SourceSpan pstate, sass::string var, ExpressionObj val, bool is_default = false, bool is_global = false); - ATTACH_AST_OPERATIONS(Assignment) - ATTACH_CRTP_PERFORM_METHODS() - }; - - //////////////////////////////////////////////////////////////////////////// - // Import directives. CSS and Sass import lists can be intermingled, so it's - // necessary to store a list of each in an Import node. - //////////////////////////////////////////////////////////////////////////// - class Import final : public Statement { - sass::vector urls_; - sass::vector incs_; - ADD_PROPERTY(List_Obj, import_queries); - public: - Import(SourceSpan pstate); - sass::vector& incs(); - sass::vector& urls(); - ATTACH_AST_OPERATIONS(Import) - ATTACH_CRTP_PERFORM_METHODS() - }; - - // not yet resolved single import - // so far we only know requested name - class Import_Stub final : public Statement { - Include resource_; - public: - Import_Stub(SourceSpan pstate, Include res); - Include resource(); - sass::string imp_path(); - sass::string abs_path(); - ATTACH_AST_OPERATIONS(Import_Stub) - ATTACH_CRTP_PERFORM_METHODS() - }; - - ////////////////////////////// - // The Sass `@warn` directive. - ////////////////////////////// - class WarningRule final : public Statement { - ADD_PROPERTY(ExpressionObj, message) - public: - WarningRule(SourceSpan pstate, ExpressionObj msg); - ATTACH_AST_OPERATIONS(WarningRule) - ATTACH_CRTP_PERFORM_METHODS() - }; - - /////////////////////////////// - // The Sass `@error` directive. - /////////////////////////////// - class ErrorRule final : public Statement { - ADD_PROPERTY(ExpressionObj, message) - public: - ErrorRule(SourceSpan pstate, ExpressionObj msg); - ATTACH_AST_OPERATIONS(ErrorRule) - ATTACH_CRTP_PERFORM_METHODS() - }; - - /////////////////////////////// - // The Sass `@debug` directive. - /////////////////////////////// - class DebugRule final : public Statement { - ADD_PROPERTY(ExpressionObj, value) - public: - DebugRule(SourceSpan pstate, ExpressionObj val); - ATTACH_AST_OPERATIONS(DebugRule) - ATTACH_CRTP_PERFORM_METHODS() - }; - - /////////////////////////////////////////// - // CSS comments. These may be interpolated. - /////////////////////////////////////////// - class Comment final : public Statement { - ADD_PROPERTY(String_Obj, text) - ADD_PROPERTY(bool, is_important) - public: - Comment(SourceSpan pstate, String_Obj txt, bool is_important); - virtual bool is_invisible() const override; - ATTACH_AST_OPERATIONS(Comment) - ATTACH_CRTP_PERFORM_METHODS() - }; - - //////////////////////////////////// - // The Sass `@if` control directive. - //////////////////////////////////// - class If final : public ParentStatement { - ADD_PROPERTY(ExpressionObj, predicate) - ADD_PROPERTY(Block_Obj, alternative) - public: - If(SourceSpan pstate, ExpressionObj pred, Block_Obj con, Block_Obj alt = {}); - virtual bool has_content() override; - ATTACH_AST_OPERATIONS(If) - ATTACH_CRTP_PERFORM_METHODS() - }; - - ///////////////////////////////////// - // The Sass `@for` control directive. - ///////////////////////////////////// - class ForRule final : public ParentStatement { - ADD_CONSTREF(sass::string, variable) - ADD_PROPERTY(ExpressionObj, lower_bound) - ADD_PROPERTY(ExpressionObj, upper_bound) - ADD_PROPERTY(bool, is_inclusive) - public: - ForRule(SourceSpan pstate, sass::string var, ExpressionObj lo, ExpressionObj hi, Block_Obj b, bool inc); - ATTACH_AST_OPERATIONS(ForRule) - ATTACH_CRTP_PERFORM_METHODS() - }; - - ////////////////////////////////////// - // The Sass `@each` control directive. - ////////////////////////////////////// - class EachRule final : public ParentStatement { - ADD_PROPERTY(sass::vector, variables) - ADD_PROPERTY(ExpressionObj, list) - public: - EachRule(SourceSpan pstate, sass::vector vars, ExpressionObj lst, Block_Obj b); - ATTACH_AST_OPERATIONS(EachRule) - ATTACH_CRTP_PERFORM_METHODS() - }; - - /////////////////////////////////////// - // The Sass `@while` control directive. - /////////////////////////////////////// - class WhileRule final : public ParentStatement { - ADD_PROPERTY(ExpressionObj, predicate) - public: - WhileRule(SourceSpan pstate, ExpressionObj pred, Block_Obj b); - ATTACH_AST_OPERATIONS(WhileRule) - ATTACH_CRTP_PERFORM_METHODS() - }; - - ///////////////////////////////////////////////////////////// - // The @return directive for use inside SassScript functions. - ///////////////////////////////////////////////////////////// - class Return final : public Statement { - ADD_PROPERTY(ExpressionObj, value) - public: - Return(SourceSpan pstate, ExpressionObj val); - ATTACH_AST_OPERATIONS(Return) - ATTACH_CRTP_PERFORM_METHODS() - }; - - ///////////////////////////////////////////////////////////////////////////// - // Definitions for both mixins and functions. The two cases are distinguished - // by a type tag. - ///////////////////////////////////////////////////////////////////////////// - class Definition final : public ParentStatement { - public: - enum Type { MIXIN, FUNCTION }; - ADD_CONSTREF(sass::string, name) - ADD_PROPERTY(Parameters_Obj, parameters) - ADD_PROPERTY(Env*, environment) - ADD_PROPERTY(Type, type) - ADD_PROPERTY(Native_Function, native_function) - ADD_PROPERTY(Sass_Function_Entry, c_function) - ADD_PROPERTY(void*, cookie) - ADD_PROPERTY(bool, is_overload_stub) - ADD_PROPERTY(Signature, signature) - public: - Definition(SourceSpan pstate, - sass::string n, - Parameters_Obj params, - Block_Obj b, - Type t); - Definition(SourceSpan pstate, - Signature sig, - sass::string n, - Parameters_Obj params, - Native_Function func_ptr, - bool overload_stub = false); - Definition(SourceSpan pstate, - Signature sig, - sass::string n, - Parameters_Obj params, - Sass_Function_Entry c_func); - ATTACH_AST_OPERATIONS(Definition) - ATTACH_CRTP_PERFORM_METHODS() - }; - - ////////////////////////////////////// - // Mixin calls (i.e., `@include ...`). - ////////////////////////////////////// - class Mixin_Call final : public ParentStatement { - ADD_CONSTREF(sass::string, name) - ADD_PROPERTY(Arguments_Obj, arguments) - ADD_PROPERTY(Parameters_Obj, block_parameters) - public: - Mixin_Call(SourceSpan pstate, sass::string n, Arguments_Obj args, Parameters_Obj b_params = {}, Block_Obj b = {}); - ATTACH_AST_OPERATIONS(Mixin_Call) - ATTACH_CRTP_PERFORM_METHODS() - }; - - /////////////////////////////////////////////////// - // The @content directive for mixin content blocks. - /////////////////////////////////////////////////// - class Content final : public Statement { - ADD_PROPERTY(Arguments_Obj, arguments) - public: - Content(SourceSpan pstate, Arguments_Obj args); - ATTACH_AST_OPERATIONS(Content) - ATTACH_CRTP_PERFORM_METHODS() - }; - - //////////////////////////////////////////////////////////////////////////// - // Arithmetic negation (logical negation is just an ordinary function call). - //////////////////////////////////////////////////////////////////////////// - class Unary_Expression final : public Expression { - public: - enum Type { PLUS, MINUS, NOT, SLASH }; - private: - HASH_PROPERTY(Type, optype) - HASH_PROPERTY(ExpressionObj, operand) - mutable size_t hash_; - public: - Unary_Expression(SourceSpan pstate, Type t, ExpressionObj o); - const sass::string type_name(); - virtual bool operator==(const Expression& rhs) const override; - size_t hash() const override; - ATTACH_AST_OPERATIONS(Unary_Expression) - ATTACH_CRTP_PERFORM_METHODS() - }; - - //////////////////////////////////////////////////////////// - // Individual argument objects for mixin and function calls. - //////////////////////////////////////////////////////////// - class Argument final : public Expression { - HASH_PROPERTY(ExpressionObj, value) - HASH_CONSTREF(sass::string, name) - ADD_PROPERTY(bool, is_rest_argument) - ADD_PROPERTY(bool, is_keyword_argument) - mutable size_t hash_; - public: - Argument(SourceSpan pstate, ExpressionObj val, sass::string n = "", bool rest = false, bool keyword = false); - void set_delayed(bool delayed) override; - bool operator==(const Expression& rhs) const override; - size_t hash() const override; - ATTACH_AST_OPERATIONS(Argument) - ATTACH_CRTP_PERFORM_METHODS() - }; - - //////////////////////////////////////////////////////////////////////// - // Argument lists -- in their own class to facilitate context-sensitive - // error checking (e.g., ensuring that all ordinal arguments precede all - // named arguments). - //////////////////////////////////////////////////////////////////////// - class Arguments final : public Expression, public Vectorized { - ADD_PROPERTY(bool, has_named_arguments) - ADD_PROPERTY(bool, has_rest_argument) - ADD_PROPERTY(bool, has_keyword_argument) - protected: - void adjust_after_pushing(Argument_Obj a) override; - public: - Arguments(SourceSpan pstate); - void set_delayed(bool delayed) override; - Argument_Obj get_rest_argument(); - Argument_Obj get_keyword_argument(); - ATTACH_AST_OPERATIONS(Arguments) - ATTACH_CRTP_PERFORM_METHODS() - }; - - - // A Media StyleRule before it has been evaluated - // Could be already final or an interpolation - class MediaRule final : public ParentStatement { - ADD_PROPERTY(List_Obj, schema) - public: - MediaRule(SourceSpan pstate, Block_Obj block = {}); - - bool bubbles() override { return true; }; - bool is_invisible() const override { return false; }; - ATTACH_AST_OPERATIONS(MediaRule) - ATTACH_CRTP_PERFORM_METHODS() - }; - - // A Media StyleRule after it has been evaluated - // Representing the static or resulting css - class CssMediaRule final : public ParentStatement, - public Vectorized { - public: - CssMediaRule(SourceSpan pstate, Block_Obj b); - bool bubbles() override { return true; }; - bool isInvisible() const { return empty(); } - bool is_invisible() const override { return false; }; - - public: - // Hash and equality implemtation from vector - size_t hash() const override { return Vectorized::hash(); } - // Check if two instances are considered equal - bool operator== (const CssMediaRule& rhs) const { - return Vectorized::operator== (rhs); - } - bool operator!=(const CssMediaRule& rhs) const { - // Invert from equality - return !(*this == rhs); - } - - ATTACH_AST_OPERATIONS(CssMediaRule) - ATTACH_CRTP_PERFORM_METHODS() - }; - - // Media Queries after they have been evaluated - // Representing the static or resulting css - class CssMediaQuery final : public AST_Node { - - // The modifier, probably either "not" or "only". - // This may be `null` if no modifier is in use. - ADD_PROPERTY(sass::string, modifier); - - // The media type, for example "screen" or "print". - // This may be `null`. If so, [features] will not be empty. - ADD_PROPERTY(sass::string, type); - - // Feature queries, including parentheses. - ADD_PROPERTY(sass::vector, features); - - public: - CssMediaQuery(SourceSpan pstate); - - // Check if two instances are considered equal - bool operator== (const CssMediaQuery& rhs) const; - bool operator!=(const CssMediaQuery& rhs) const { - // Invert from equality - return !(*this == rhs); - } - - // Returns true if this query is empty - // Meaning it has no type and features - bool empty() const { - return type_.empty() - && modifier_.empty() - && features_.empty(); - } - - // Whether this media query matches all media types. - bool matchesAllTypes() const { - return type_.empty() || Util::equalsLiteral("all", type_); - } - - // Merges this with [other] and adds a query that matches the intersection - // of both inputs to [result]. Returns false if the result is unrepresentable - CssMediaQuery_Obj merge(CssMediaQuery_Obj& other); - - ATTACH_AST_OPERATIONS(CssMediaQuery) - ATTACH_CRTP_PERFORM_METHODS() - }; - - //////////////////////////////////////////////////// - // Media queries (replaced by MediaRule at al). - // ToDo: only used for interpolation case - //////////////////////////////////////////////////// - class Media_Query final : public Expression, - public Vectorized { - ADD_PROPERTY(String_Obj, media_type) - ADD_PROPERTY(bool, is_negated) - ADD_PROPERTY(bool, is_restricted) - public: - Media_Query(SourceSpan pstate, String_Obj t = {}, size_t s = 0, bool n = false, bool r = false); - ATTACH_AST_OPERATIONS(Media_Query) - ATTACH_CRTP_PERFORM_METHODS() - }; - - //////////////////////////////////////////////////// - // Media expressions (for use inside media queries). - // ToDo: only used for interpolation case - //////////////////////////////////////////////////// - class Media_Query_Expression final : public Expression { - ADD_PROPERTY(ExpressionObj, feature) - ADD_PROPERTY(ExpressionObj, value) - ADD_PROPERTY(bool, is_interpolated) - public: - Media_Query_Expression(SourceSpan pstate, ExpressionObj f, ExpressionObj v, bool i = false); - ATTACH_AST_OPERATIONS(Media_Query_Expression) - ATTACH_CRTP_PERFORM_METHODS() - }; - - ///////////////////////////////////////////////// - // At root expressions (for use inside @at-root). - ///////////////////////////////////////////////// - class At_Root_Query final : public Expression { - private: - ADD_PROPERTY(ExpressionObj, feature) - ADD_PROPERTY(ExpressionObj, value) - public: - At_Root_Query(SourceSpan pstate, ExpressionObj f = {}, ExpressionObj v = {}, bool i = false); - bool exclude(sass::string str); - ATTACH_AST_OPERATIONS(At_Root_Query) - ATTACH_CRTP_PERFORM_METHODS() - }; - - /////////// - // At-root. - /////////// - class AtRootRule final : public ParentStatement { - ADD_PROPERTY(At_Root_Query_Obj, expression) - public: - AtRootRule(SourceSpan pstate, Block_Obj b = {}, At_Root_Query_Obj e = {}); - bool bubbles() override; - bool exclude_node(Statement_Obj s); - ATTACH_AST_OPERATIONS(AtRootRule) - ATTACH_CRTP_PERFORM_METHODS() - }; - - ///////////////////////////////////////////////////////// - // Individual parameter objects for mixins and functions. - ///////////////////////////////////////////////////////// - class Parameter final : public AST_Node { - ADD_CONSTREF(sass::string, name) - ADD_PROPERTY(ExpressionObj, default_value) - ADD_PROPERTY(bool, is_rest_parameter) - public: - Parameter(SourceSpan pstate, sass::string n, ExpressionObj def = {}, bool rest = false); - ATTACH_AST_OPERATIONS(Parameter) - ATTACH_CRTP_PERFORM_METHODS() - }; - - ///////////////////////////////////////////////////////////////////////// - // Parameter lists -- in their own class to facilitate context-sensitive - // error checking (e.g., ensuring that all optional parameters follow all - // required parameters). - ///////////////////////////////////////////////////////////////////////// - class Parameters final : public AST_Node, public Vectorized { - ADD_PROPERTY(bool, has_optional_parameters) - ADD_PROPERTY(bool, has_rest_parameter) - protected: - void adjust_after_pushing(Parameter_Obj p) override; - public: - Parameters(SourceSpan pstate); - ATTACH_AST_OPERATIONS(Parameters) - ATTACH_CRTP_PERFORM_METHODS() - }; - -} - -#include "ast_values.hpp" -#include "ast_supports.hpp" +#include "ast_fwd_decl.hpp" +#include "ast_helpers.hpp" +#include "ast_nodes.hpp" +#include "ast_imports.hpp" #include "ast_selectors.hpp" - -#ifdef __clang__ - -// #pragma clang diagnostic pop -// #pragma clang diagnostic push - -#endif +#include "ast_statements.hpp" +#include "ast_supports.hpp" +#include "ast_values.hpp" #endif diff --git a/src/ast2.hpp b/src/ast2.hpp new file mode 100644 index 0000000000..2d6ef1bef5 --- /dev/null +++ b/src/ast2.hpp @@ -0,0 +1,1560 @@ +#ifndef SASS_AST_H +#define SASS_AST_H + +// sass.hpp must go before all system headers to get the +// __EXTENSIONS__ fix on Solaris. +#include "sass.hpp" + +#include +#include +#include + +#include "sass/base.h" +#include "ast_helpers.hpp" +#include "ast_fwd_decl.hpp" +#include "ast_def_macros.hpp" +#include "ordered_map.hpp" + +#include "file.hpp" +#include "memory.hpp" +#include "mapping.hpp" +#include "position.hpp" +#include "operation.hpp" +#include "environment.hpp" +#include "fn_utils.hpp" +#include "environment_stack.hpp" + +#include "ordered-map/ordered_map.h" + +namespace Sass { + + uint8_t sass_op_to_precedence(enum Sass_OP op); + + const char* sass_op_to_name(enum Sass_OP op); + + const char* sass_op_separator(enum Sass_OP op); + + ////////////////////////////////////////////////////////// + // Abstract base class for all abstract syntax tree nodes. + ////////////////////////////////////////////////////////// + class AST_Node : public SharedObj { + ADD_PROPERTY(SourceSpan, pstate); + public: + AST_Node(const SourceSpan& pstate) + : SharedObj(), pstate_(pstate) + { } + AST_Node(SourceSpan&& pstate) + : SharedObj(), pstate_(std::move(pstate)) + { } + AST_Node(const AST_Node* ptr) + : pstate_(ptr->pstate_) + { } + + // void* operator new(size_t nbytes) { + // return ::operator new(nbytes); + // } + // + // void operator delete(void* ptr) { + // return ::operator delete(ptr); + // } + + // allow implicit conversion to string + // needed for by SharedPtr implementation + operator sass::string() { + return to_string(); + } + + // AST_Node(AST_Node& ptr) = delete; + + virtual ~AST_Node() = 0; + virtual size_t hash() const { return 0; } + virtual sass::string inspect() const { return to_string({ INSPECT, 5 }); } + virtual sass::string to_sass() const { return to_string({ TO_SASS, 5 }); } + virtual sass::string to_string(Sass_Inspect_Options opt) const; + virtual sass::string to_css(Sass_Inspect_Options opt, bool quotes = false) const; + virtual sass::string to_css(Sass_Inspect_Options opt, sass::vector& mappings, bool quotes = false) const; + virtual sass::string to_string() const; + virtual sass::string to_css(sass::vector& mappings, bool quotes = false) const; + virtual sass::string to_css(bool quotes = false) const; + virtual void cloneChildren() {}; + // generic find function (not fully implemented yet) + // ToDo: add specific implementions to all children + virtual bool find ( bool (*f)(AST_Node_Obj) ) { return f(this); }; + // Offset off() { return pstate(); } + // Position pos() { return pstate(); } + + // Subclasses should only override these methods + // The full set is emulated by calling only those + // Make sure the left side is resonably upcasted! + virtual bool operator< (const AST_Node& rhs) const { + throw std::runtime_error("operator< not implemented"); + } + virtual bool operator== (const AST_Node& rhs) const { + throw std::runtime_error("operator== not implemented"); + } + + + // We can give some reasonable implementations by using + // inverst operators on the specialized implementations + virtual bool operator>(const AST_Node& rhs) const { return rhs < *this; }; + virtual bool operator<=(const AST_Node& rhs) const { return !(rhs < *this); }; + virtual bool operator>=(const AST_Node& rhs) const { return !(*this < rhs); }; + virtual bool operator!=(const AST_Node& rhs) const { return !(*this == rhs); } + + ATTACH_ABSTRACT_COPY_OPERATIONS(AST_Node); + ATTACH_ABSTRACT_CRTP_PERFORM_METHODS() + }; + inline AST_Node::~AST_Node() { } + + ////////////////////////////////////////////////////////////////////// + // define cast template now (need complete type) + ////////////////////////////////////////////////////////////////////// + + template + T* Cast(AST_Node* ptr) { + return ptr && typeid(T) == typeid(*ptr) ? + static_cast(ptr) : NULL; + }; + + template + const T* Cast(const AST_Node* ptr) { + return ptr && typeid(T) == typeid(*ptr) ? + static_cast(ptr) : NULL; + }; + + ////////////////////////////////////////////////////////////////////// + ////////////////////////////////////////////////////////////////////// + + class SassNode : public AST_Node { + public: + SassNode(const SourceSpan& pstate) : + AST_Node(pstate) {}; + SassNode(SourceSpan&& pstate) : + AST_Node(std::move(pstate)) {}; + ATTACH_VIRTUAL_COPY_OPERATIONS(SassNode); + ATTACH_CRTP_PERFORM_METHODS(); + }; + + class CallableInvocation { + // The arguments passed to the callable. + ADD_PROPERTY(ArgumentInvocationObj, arguments); + public: + CallableInvocation( + ArgumentInvocation* arguments) : + arguments_(arguments) {} + }; + + + class ArgumentDeclaration : public SassNode { + + // The arguments that are taken. + ADD_CONSTREF(sass::vector, arguments); + + // The name of the rest argument (as in `$args...`), + // or `null` if none was declared. + ADD_CONSTREF(sass::string, restArg); + + public: + + ArgumentDeclaration( + const SourceSpan& pstate, + const sass::vector& arguments, + const sass::string& restArg = ""); + + bool isEmpty() const { + return arguments_.empty() + && restArg_.empty(); + } + + static ArgumentDeclaration* parse( + Context& context, + const sass::string& contents); + + void verify( + size_t positional, + const KeywordMap& names, + const SourceSpan& pstate, + const Backtraces& traces); + + bool matches( + size_t positional, + const KeywordMap& names); + + sass::string toString2() const; + + }; + + + /// The result of evaluating arguments to a function or mixin. + class ArgumentResults2 { + + // Arguments passed by position. + ADD_REF(sass::vector, positional); + + // The [AstNode]s that hold the spans for each [positional] + // argument, or `null` if source span tracking is disabled. This + // stores [AstNode]s rather than [FileSpan]s so it can avoid + // calling [AstNode.span] if the span isn't required, since + // some nodes need to do real work to manufacture a source span. + // sass::vector positionalNodes; + + // Arguments passed by name. + // A list implementation might be more efficient + // I dont expect any function to have many arguments + // Normally the tradeoff is around 8 items in the list + ADD_REF(KeywordMap, named); + + // The [AstNode]s that hold the spans for each [named] argument, + // or `null` if source span tracking is disabled. This stores + // [AstNode]s rather than [FileSpan]s so it can avoid calling + // [AstNode.span] if the span isn't required, since some nodes + // need to do real work to manufacture a source span. + // KeywordMap namedNodes; + + // The separator used for the rest argument list, if any. + ADD_REF(Sass_Separator, separator); + + public: + + ArgumentResults2() {}; + + ArgumentResults2( + const ArgumentResults2& other); + + ArgumentResults2( + ArgumentResults2&& other); + + ArgumentResults2& operator=( + ArgumentResults2&& other); + + ArgumentResults2( + const sass::vector& positional, + const KeywordMap& named, + Sass_Separator separator); + + ArgumentResults2( + sass::vector && positional, + KeywordMap&& named, + Sass_Separator separator); + + }; + + + class ArgumentInvocation : public SassNode { + + // The arguments passed by position. + ADD_REF(sass::vector, positional); + public: + // + ArgumentResults2 evaluated; + + // The arguments passed by name. + ADD_CONSTREF(KeywordMap, named); + + // The first rest argument (as in `$args...`). + ADD_PROPERTY(ExpressionObj, restArg); + + // The second rest argument, which is expected to only contain a keyword map. + ADD_PROPERTY(ExpressionObj, kwdRest); + + public: + + ArgumentInvocation(const SourceSpan& pstate, + const sass::vector& positional, + const KeywordMap& named, + Expression* restArgs = nullptr, + Expression* kwdRest = nullptr); + + // Returns whether this invocation passes no arguments. + bool isEmpty() const; + + sass::string toString() const; + + ATTACH_CRTP_PERFORM_METHODS(); + + }; + + ////////////////////////////////////////////////////////////////////// + // Abstract base class for expressions. This side of the AST hierarchy + // represents elements in value contexts, which exist primarily to be + // evaluated and returned. + ////////////////////////////////////////////////////////////////////// + class Expression : public SassNode { + public: + enum Type { + NONE, + BOOLEAN, + NUMBER, + COLOR, + STRING, + LIST, + MAP, + NULL_VAL, + FUNCTION_VAL, + C_WARNING, + C_ERROR, + FUNCTION, + VARIABLE, + PARENT, + NUM_TYPES + }; + private: + // expressions in some contexts shouldn't be evaluated + ADD_PROPERTY(Type, concrete_type) + public: + Expression(SourceSpan&& pstate, bool d = false, bool e = false, bool i = false, Type ct = NONE); + Expression(const SourceSpan& pstate, bool d = false, bool e = false, bool i = false, Type ct = NONE); + virtual operator bool() { return true; } + virtual ~Expression() { } + virtual bool is_invisible() const { return false; } + + virtual const sass::string& type() const { + throw std::runtime_error("Invalid reflection"); + } + + virtual Expression* withoutSlash() { + return this; + } + + virtual Expression* removeSlash() { + return this; + } + + virtual bool is_false() { return false; } + // virtual bool is_true() { return !is_false(); } + virtual bool operator== (const Expression& rhs) const { return false; } + inline bool operator!=(const Expression& rhs) const { return !(rhs == *this); } + ATTACH_VIRTUAL_COPY_OPERATIONS(Expression); + size_t hash() const override { return 0; } + }; + +} + +///////////////////////////////////////////////////////////////////////////////////// +// Hash method specializations for std::unordered_map to work with Sass::Expression +///////////////////////////////////////////////////////////////////////////////////// + +namespace std { + template<> + struct hash + { + size_t operator()(Sass::Expression_Obj s) const + { + return s->hash(); + } + }; + template<> + struct equal_to + { + bool operator()( Sass::Expression_Obj lhs, Sass::Expression_Obj rhs) const + { + return lhs->hash() == rhs->hash(); + } + }; +} + +namespace Sass { + + ///////////////////////////////////////////////////////////////////////////// + // Mixin class for AST nodes that should behave like vectors. Uses the + // "Template Method" design pattern to allow subclasses to adjust their flags + // when certain objects are pushed. + ///////////////////////////////////////////////////////////////////////////// + template + class Vectorized { + sass::vector elements_; + protected: + mutable size_t hash_; + void reset_hash() { hash_ = 0; } + public: + Vectorized(size_t s = 0) : hash_(0) + { elements_.reserve(s); } + Vectorized(const Vectorized* vec) : + elements_(vec->elements_), + hash_(0) + {} + Vectorized(sass::vector vec) : + elements_(std::move(vec)), + hash_(0) + {} + ~Vectorized() {}; + size_t length() const { return elements_.size(); } + bool empty() const { return elements_.empty(); } + void clear() { return elements_.clear(); } + T& last() { return elements_.back(); } + T& first() { return elements_.front(); } + const T& last() const { return elements_.back(); } + const T& first() const { return elements_.front(); } + + bool operator== (const Vectorized& rhs) const { + // Abort early if sizes do not match + if (length() != rhs.length()) return false; + // Otherwise test each node for object equalicy in order + return std::equal(begin(), end(), rhs.begin(), ObjEqualityFn); + } + + bool operator!= (const Vectorized& rhs) const { + return !(*this == rhs); + } + + T& operator[](size_t i) { return elements_[i]; } + virtual const T& at(size_t i) const { return elements_.at(i); } + virtual T& at(size_t i) { return elements_.at(i); } + const T& get(size_t i) const { return elements_[i]; } + // ToDo: might insert am item (update ordered list) + const T& operator[](size_t i) const { return elements_[i]; } + + // Implicitly get the std::vector from our object + // Makes the Vector directly assignable to std::vector + // You are responsible to make a copy if needed + // Note: since this returns the real object, we can't + // Note: guarantee that the hash will not get out of sync + operator sass::vector&() { return elements_; } + operator const sass::vector&() const { return elements_; } + + // Explicitly request all elements as a real std::vector + // You are responsible to make a copy if needed + // Note: since this returns the real object, we can't + // Note: guarantee that the hash will not get out of sync + sass::vector& elements() { return elements_; } + const sass::vector& elements() const { return elements_; } + + // Insert all items from compatible vector + void concat(const sass::vector& v) + { + if (!v.empty()) reset_hash(); + elements().insert(end(), v.begin(), v.end()); + } + + // Insert all items from compatible vector + void concat(sass::vector&& v) + { + if (!v.empty()) reset_hash(); + std::move(v.begin(), v.end(), + std::back_inserter(elements_)); + // elements().insert(end(), v.begin(), v.end()); + } + + + // Syntatic sugar for pointers + void concat(const Vectorized* v) + { + if (v != nullptr) { + return concat(*v); + } + } + + // Insert one item on the front + void unshift(T element) + { + reset_hash(); + elements_.insert(begin(), element); + } + + // Remove and return item on the front + // ToDo: handle empty vectors + T shift() { + reset_hash(); + T first = get(0); + elements_.erase(begin()); + return first; + } + + // Insert one item on the back + // ToDo: rename this to push + void append(T element) + { + if (!element) { + std::cerr << "APPEND NULL PTR\n"; + } + reset_hash(); + elements_.emplace_back(element); + } + + // Check if an item already exists + // Uses underlying object `operator==` + // E.g. compares the actual objects + bool contains(const T& el) const { + for (const T& rhs : elements_) { + // Test the underlying objects for equality + // A std::find checks for pointer equality + if (ObjEqualityFn(el, rhs)) { + return true; + } + } + return false; + } + + // This might be better implemented as `operator=`? + void elements(sass::vector e) { + reset_hash(); + elements_ = std::move(e); + } + + virtual size_t hash() const + { + if (hash_ == 0) { + for (const T& el : elements_) { + hash_combine(hash_, el->hash()); + } + } + return hash_; + } + + template + typename sass::vector::iterator insert(P position, const V& val) { + reset_hash(); + return elements_.insert(position, val); + } + + typename sass::vector::iterator end() { return elements_.end(); } + typename sass::vector::iterator begin() { return elements_.begin(); } + typename sass::vector::const_iterator end() const { return elements_.end(); } + typename sass::vector::const_iterator begin() const { return elements_.begin(); } + typename sass::vector::iterator erase(typename sass::vector::iterator el) { reset_hash(); return elements_.erase(el); } + typename sass::vector::const_iterator erase(typename sass::vector::const_iterator el) { reset_hash(); return elements_.erase(el); } + + }; + + ///////////////////////////////////////////////////////////////////////////// + // Mixin class for AST nodes that should behave like a hash table. Uses an + // extra internally to maintain insertion order for interation. + ///////////////////////////////////////////////////////////////////////////// + template + class Hashed { + + + using ordered_map_type = typename tsl::ordered_map< + K, T, ObjHash, ObjEquality, + SassAllocator>, + sass::vector> + >; + + protected: + + ordered_map_type elements_; + + mutable size_t hash_; + + void reset_hash() { hash_ = 0; } + + public: + + Hashed() + : elements_(), + hash_(0) + { + // elements_.reserve(s); + } + + // Copy constructor + Hashed(const Hashed& copy) : + elements_(), hash_(0) + { + // this seems to expensive!? + // elements_.reserve(copy.size()); + elements_ = copy.elements_; + }; + + // Move constructor + Hashed(Hashed&& move) : + elements_(std::move(move.elements_)), + hash_(move.hash_) {}; + + virtual ~Hashed(); + + size_t size() const { return elements_.size(); } + bool empty() const { return elements_.empty(); } + + bool has(K k) const { + return elements_.count(k) == 1; + } + + void reserve(size_t size) + { + elements_.reserve(size); + } + + T at(K k) const { + auto it = elements_.find(k); + if (it == elements_.end()) return {}; + else return it->second; + } + + bool erase(K key) + { + reset_hash(); + return elements_.erase(key); + } + + void set(std::pair& kv) + { + reset_hash(); + elements_[kv.first] = kv.second; + } + + void insert(K key, T val) + { + reset_hash(); + elements_[key] = val; + } + + void concat(Hashed arr) + { + reset_hash(); + for (const auto& kv : arr) { + elements_[kv.first] = kv.second; + } + // elements_.append(arr.elements()); + } + + // Return unmodifiable reference + const ordered_map_type& elements() const { + return elements_; + } + + const sass::vector keys() const { + sass::vector list; + for (auto kv : elements_) { + list.emplace_back(kv.first); + } + return list; + } + const sass::vector values() const { + sass::vector list; + for (auto kv : elements_) { + list.emplace_back(kv.second); + } + return list; + } + + typename ordered_map_type::iterator end() { return elements_.end(); } + typename ordered_map_type::iterator begin() { return elements_.begin(); } + typename ordered_map_type::const_iterator end() const { return elements_.end(); } + typename ordered_map_type::const_iterator begin() const { return elements_.begin(); } + + }; + + template + inline Hashed::~Hashed() { } + + ///////////////////////////////////////////////////////////////////////// + // Abstract base class for statements. This side of the AST hierarchy + // represents elements in expansion contexts, which exist primarily to be + // rewritten and macro-expanded. + ///////////////////////////////////////////////////////////////////////// + class Statement : public AST_Node { + private: + ADD_PROPERTY(size_t, tabs) + ADD_PROPERTY(bool, group_end) + public: + Statement(SourceSpan&& pstate, size_t t = 0); + Statement(const SourceSpan& pstate, size_t t = 0); + virtual ~Statement() = 0; // virtual destructor + // needed for rearranging nested rulesets during CSS emission + virtual bool bubbles() const; + virtual bool has_content(); + virtual bool is_invisible() const; + ATTACH_VIRTUAL_COPY_OPERATIONS(Statement) + }; + inline Statement::~Statement() { } + + //////////////////////// + // Blocks of statements. + //////////////////////// + class Block final : public Statement, public Vectorized { + ADD_POINTER(IDXS*, idxs); + ADD_PROPERTY(bool, is_root); + // needed for properly formatted CSS emission + public: + Block(const SourceSpan& pstate, size_t s = 0, bool r = false); + Block(const SourceSpan& pstate, const sass::vector& vec, bool r = false); + Block(const SourceSpan& pstate, sass::vector&& vec, bool r = false); + bool isInvisible() const; + bool is_invisible() const override { + return isInvisible(); + } + bool has_content() override; + // ATTACH_CLONE_OPERATIONS(Block) + ATTACH_CRTP_PERFORM_METHODS() + }; + + //////////////////////////////////////////////////////////////////////// + // Abstract base class for statements that contain blocks of statements. + //////////////////////////////////////////////////////////////////////// + + // [X] AtRootRule + // [X] AtRule + // [X] CallableDeclaration + // [X] Declaration + // [X] EachRule + // [X] ForRule + // [X] MediaRule + // [X] StyleRule + // [ ] Stylesheet + // [X] SupportsRule + // [X] WhileRule + class ParentStatement : public Statement { + ADD_PROPERTY(Block_Obj, block) + public: + void concat(const sass::vector& vec) { + if (block_.ptr() == nullptr) { + block_ = SASS_MEMORY_NEW(Block, pstate_); + } + block_->concat(vec); + } + void concat(sass::vector&& vec) { + if (block_.ptr() == nullptr) { + block_ = SASS_MEMORY_NEW(Block, pstate_); + } + block_->concat(std::move(vec)); + } + ParentStatement(SourceSpan&& pstate, Block_Obj b); + ParentStatement(const SourceSpan& pstate, Block_Obj b); + ParentStatement(const ParentStatement* ptr); // copy constructor + virtual ~ParentStatement() = 0; // virtual destructor + virtual bool has_content() override; + }; + inline ParentStatement::~ParentStatement() { } + + ///////////////////////////////////////////////////////////////////////////// + // A style rule. This applies style declarations to elements + // that match a given selector. Formerly known as `Ruleset`. + ///////////////////////////////////////////////////////////////////////////// + class StyleRule final : public ParentStatement { + // The selector to which the declaration will be applied. + // This is only parsed after the interpolation has been resolved. + ADD_PROPERTY(InterpolationObj, interpolation); + ADD_POINTER(IDXS*, idxs); + public: + StyleRule(SourceSpan&& pstate, Interpolation* s, Block_Obj b = {}); + bool empty() const { return block().isNull() || block()->empty(); } + // ATTACH_CLONE_OPERATIONS(StyleRule) + ATTACH_CRTP_PERFORM_METHODS() + }; + + // ToDo: ParentStatement + /////////////////////////////////////////////////////////////////////// + // At-rules -- arbitrary directives beginning with "@" that may have an + // optional statement block. + /////////////////////////////////////////////////////////////////////// + class AtRule final : public ParentStatement { + ADD_PROPERTY(InterpolationObj, name); + ADD_PROPERTY(InterpolationObj, value); + public: + AtRule(const SourceSpan& pstate, + InterpolationObj name, + ExpressionObj value, + Block_Obj b = {}); + ATTACH_CLONE_OPERATIONS(AtRule); + ATTACH_CRTP_PERFORM_METHODS(); + }; + + ///////////////// + // Bubble. + ///////////////// + class Bubble final : public Statement { + ADD_PROPERTY(Statement_Obj, node) + ADD_PROPERTY(bool, group_end) + public: + Bubble(const SourceSpan& pstate, Statement_Obj n, Statement_Obj g = {}, size_t t = 0); + bool bubbles() const override final; + // ATTACH_CLONE_OPERATIONS(Bubble) + ATTACH_CRTP_PERFORM_METHODS() + }; + + ///////////////// + // Trace. + ///////////////// + class Trace final : public ParentStatement { + ADD_CONSTREF(char, type) + ADD_CONSTREF(sass::string, name) + public: + Trace(const SourceSpan& pstate, const sass::string& name, Block_Obj b = {}, char type = 'm'); + ATTACH_CRTP_PERFORM_METHODS() + }; + + /////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////// + + // An expression that directly embeds a [Value]. This is never + // constructed by the parser. It's only used when ASTs are + // constructed dynamically, as for the `call()` function. + class ValueExpression : public Expression { + ADD_PROPERTY(ValueObj, value); + public: + ValueExpression( + const SourceSpan& pstate, + ValueObj value); + ATTACH_CRTP_PERFORM_METHODS(); + }; + + class ListExpression : public Expression { + ADD_CONSTREF(sass::vector, contents); + ADD_PROPERTY(Sass_Separator, separator); + ADD_PROPERTY(bool, hasBrackets); + public: + ListExpression(const SourceSpan& pstate, Sass_Separator separator = SASS_UNDEF); + void concat(sass::vector& expressions) { + std::copy( + expressions.begin(), expressions.end(), + std::back_inserter(contents_) + ); + } + size_t size() const { + return contents_.size(); + } + Expression* get(size_t i) { + return contents_[i]; + } + void append(Expression* expression) { + contents_.emplace_back(expression); + } + sass::string toString() { + // var buffer = StringBuffer(); + // if (hasBrackets) buffer.writeCharCode($lbracket); + // buffer.write(contents + // .map((element) = > + // _elementNeedsParens(element) ? "($element)" : element.toString()) + // .join(separator == ListSeparator.comma ? ", " : " ")); + // if (hasBrackets) buffer.writeCharCode($rbracket); + // return buffer.toString(); + return "ListExpression"; + } + // Returns whether [expression], contained in [this], + // needs parentheses when printed as Sass source. + bool _elementNeedsParens(Expression* expression) { + /* + if (expression is ListExpression) { + if (expression.contents.length < 2) return false; + if (expression.hasBrackets) return false; + return separator == ListSeparator.comma + ? separator == ListSeparator.comma + : separator != ListSeparator.undecided; + } + + if (separator != ListSeparator.space) return false; + + if (expression is UnaryOperationExpression) { + return expression.operator == UnaryOperator.plus || + expression.operator == UnaryOperator.minus; + } + + */ + return false; + } + ATTACH_CRTP_PERFORM_METHODS(); + }; + + /////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////// + + class MapExpression final : public Expression { + ADD_CONSTREF(sass::vector, kvlist); + public: + void append(Expression* kv) { + kvlist_.emplace_back(kv); + } + size_t size() const { + return kvlist_.size(); + } + Expression* get(size_t i) { + return kvlist_[i]; + } + MapExpression(const SourceSpan& pstate); + ATTACH_CRTP_PERFORM_METHODS(); + }; + + + //////////////////////////////////////////////////////////////////////// + // Declarations -- style rules consisting of a property name and values. + //////////////////////////////////////////////////////////////////////// + class Declaration final : public ParentStatement { + ADD_PROPERTY(InterpolationObj, name); + ADD_PROPERTY(ExpressionObj, value); + ADD_PROPERTY(bool, is_custom_property); + public: + Declaration(const SourceSpan& pstate, InterpolationObj name, ExpressionObj value = {}, bool c = false, Block_Obj b = {}); + bool is_invisible() const override; + // ATTACH_CLONE_OPERATIONS(Declaration) + ATTACH_CRTP_PERFORM_METHODS() + }; + + ///////////////////////////////////// + // Assignments -- variable and value. + ///////////////////////////////////// + class Assignment final : public Statement { + ADD_CONSTREF(EnvString, variable); + ADD_PROPERTY(ExpressionObj, value); + ADD_PROPERTY(IdxRef, vidx); + ADD_PROPERTY(bool, is_default); + ADD_PROPERTY(bool, is_global); + public: + Assignment(const SourceSpan& pstate, const sass::string& var, IdxRef vidx, Expression_Obj val, bool is_default = false, bool is_global = false); + // ATTACH_CLONE_OPERATIONS(Assignment) + ATTACH_CRTP_PERFORM_METHODS() + }; + + class ImportBase : public Statement { + public: + ImportBase(const SourceSpan& pstate); + ATTACH_VIRTUAL_COPY_OPERATIONS(ImportBase); + }; + + class StaticImport final : public ImportBase { + ADD_PROPERTY(InterpolationObj, url); + ADD_PROPERTY(CssStringObj, url2); + ADD_PROPERTY(SupportsCondition_Obj, supports); + ADD_PROPERTY(InterpolationObj, media); + ADD_PROPERTY(bool, outOfOrder); + public: + StaticImport(const SourceSpan& pstate, InterpolationObj url, SupportsCondition_Obj supports = {}, InterpolationObj media = {}); + // ATTACH_CLONE_OPERATIONS(StaticImport); + ATTACH_CRTP_PERFORM_METHODS(); + }; + + class DynamicImport final : public ImportBase { + ADD_CONSTREF(sass::string, url); + public: + DynamicImport(const SourceSpan& pstate, const sass::string& url); + // ATTACH_CLONE_OPERATIONS(DynamicImport); + ATTACH_CRTP_PERFORM_METHODS(); + }; + + + class ImportRule final : public Statement, public Vectorized { + public: + ImportRule(const SourceSpan& pstate); + // ATTACH_CLONE_OPERATIONS(ImportRule); + ATTACH_CRTP_PERFORM_METHODS(); + }; + + //////////////////////////////////////////////////////////////////////////// + // Import directives. CSS and Sass import lists can be intermingled, so it's + // necessary to store a list of each in an Import node. + //////////////////////////////////////////////////////////////////////////// + class Import final : public ImportBase { + sass::vector urls_; + sass::vector incs_; + // sass::vector imports_; + ADD_CONSTREF(sass::vector, import_queries); + ADD_CONSTREF(sass::vector, queries); + public: + Import(const SourceSpan& pstate); + sass::vector& incs(); + sass::vector& urls(); + // sass::vector& imports(); + sass::vector& queries2(); + bool is_invisible() const override; + // ATTACH_CLONE_OPERATIONS(Import) + ATTACH_CRTP_PERFORM_METHODS() + }; + + // not yet resolved single import + // so far we only know requested name + class Import_Stub final : public ImportBase { + Include resource_; + // Sass_Import_Entry import_; + public: + Import_Stub(const SourceSpan& pstate, Include res/*, + Sass_Import_Entry import*/); + Include resource(); + // Sass_Import_Entry import(); + sass::string imp_path(); + sass::string abs_path(); + // ATTACH_CLONE_OPERATIONS(Import_Stub) + ATTACH_CRTP_PERFORM_METHODS() + }; + + ////////////////////////////// + // The Sass `@warn` directive. + ////////////////////////////// + class WarnRule final : public Statement { + ADD_PROPERTY(ExpressionObj, expression); + public: + WarnRule(const SourceSpan& pstate, + ExpressionObj expression); + // String toString() => "@warn $expression;"; + ATTACH_CRTP_PERFORM_METHODS() + }; + + /////////////////////////////// + // The Sass `@error` directive. + /////////////////////////////// + class ErrorRule final : public Statement { + ADD_PROPERTY(ExpressionObj, expression); + public: + ErrorRule(const SourceSpan& pstate, + ExpressionObj expression); + // String toString() => "@error $expression;"; + ATTACH_CRTP_PERFORM_METHODS() + }; + + /////////////////////////////// + // The Sass `@debug` directive. + /////////////////////////////// + class DebugRule final : public Statement { + ADD_PROPERTY(ExpressionObj, expression); + public: + DebugRule(const SourceSpan& pstate, + ExpressionObj expression); + // String toString() => "@debug $expression;"; + ATTACH_CRTP_PERFORM_METHODS() + }; + + /////////////////////////////////////////// + // CSS comments. These may be interpolated. + /////////////////////////////////////////// + class LoudComment final : public Statement { + // The interpolated text of this comment, including comment characters. + ADD_PROPERTY(InterpolationObj, text) + public: + LoudComment(const SourceSpan& pstate, InterpolationObj itpl); + // ATTACH_CLONE_OPERATIONS(LoudComment) + ATTACH_CRTP_PERFORM_METHODS() + }; + + class SilentComment final : public Statement { + // The text of this comment, including comment characters. + ADD_CONSTREF(sass::string, text) + public: + SilentComment(const SourceSpan& pstate, const sass::string& text); + // not used in dart sass beside tests!? + // sass::string getDocComment() const; + // ATTACH_CLONE_OPERATIONS(SilentComment) + ATTACH_CRTP_PERFORM_METHODS() + }; + + //////////////////////////////////// + // The Sass `@if` control directive. + //////////////////////////////////// + class If final : public ParentStatement { + ADD_POINTER(IDXS*, idxs); + ADD_PROPERTY(Expression_Obj, predicate); + ADD_PROPERTY(Block_Obj, alternative); + public: + If(const SourceSpan& pstate, Expression_Obj pred, Block_Obj con, Block_Obj alt = {}); + virtual bool has_content() override; + // ATTACH_CLONE_OPERATIONS(If) + ATTACH_CRTP_PERFORM_METHODS() + }; + + ///////////////////////////////////// + // The Sass `@for` control directive. + ///////////////////////////////////// + class For final : public ParentStatement { + ADD_CONSTREF(EnvString, variable); + ADD_PROPERTY(Expression_Obj, lower_bound); + ADD_PROPERTY(Expression_Obj, upper_bound); + ADD_POINTER(IDXS*, idxs); + ADD_PROPERTY(bool, is_inclusive); + public: + For(const SourceSpan& pstate, const EnvString& var, Expression_Obj lo, Expression_Obj hi, bool inc = false, Block_Obj b = {}); + // ATTACH_CLONE_OPERATIONS(For) + ATTACH_CRTP_PERFORM_METHODS() + }; + + ////////////////////////////////////// + // The Sass `@each` control directive. + ////////////////////////////////////// + class Each final : public ParentStatement { + ADD_CONSTREF(sass::vector, variables); + ADD_POINTER(IDXS*, idxs); + ADD_PROPERTY(Expression_Obj, list); + public: + Each(const SourceSpan& pstate, const sass::vector& vars, Expression_Obj lst, Block_Obj b = {}); + // ATTACH_CLONE_OPERATIONS(Each) + ATTACH_CRTP_PERFORM_METHODS() + }; + + /////////////////////////////////////// + // The Sass `@while` control directive. + /////////////////////////////////////// + class WhileRule final : public ParentStatement { + ADD_PROPERTY(ExpressionObj, condition); + ADD_POINTER(IDXS*, idxs); + public: + WhileRule(const SourceSpan& pstate, + ExpressionObj condition, + Block_Obj b = {}); + // String toString() = > "@while $condition {${children.join(" ")}}"; + ATTACH_CRTP_PERFORM_METHODS() + }; + + ///////////////////////////////////////////////////////////// + // The @return directive for use inside SassScript functions. + ///////////////////////////////////////////////////////////// + class Return final : public Statement { + ADD_PROPERTY(Expression_Obj, value); + public: + Return(const SourceSpan& pstate, Expression_Obj val); + // ATTACH_CLONE_OPERATIONS(Return) + ATTACH_CRTP_PERFORM_METHODS() + }; + + class InvocationExpression : + public Expression, + public CallableInvocation { + public: + InvocationExpression(const SourceSpan& pstate, + ArgumentInvocation* arguments) : + Expression(pstate), + CallableInvocation(arguments) + { + } + }; + + class InvocationStatement : + public Statement, + public CallableInvocation { + public: + InvocationStatement(const SourceSpan& pstate, + ArgumentInvocation* arguments) : + Statement(pstate), + CallableInvocation(arguments) + { + } + }; + + /// A function invocation. + /// + /// This may be a plain CSS function or a Sass function. + class IfExpression : public InvocationExpression { + + public: + IfExpression(const SourceSpan& pstate, + ArgumentInvocation* arguments) : + InvocationExpression(pstate, arguments) + { + } + + sass::string toString() const { + return "if" + arguments_->toString(); + } + + ATTACH_CRTP_PERFORM_METHODS(); + }; + + /// A function invocation. + /// + /// This may be a plain CSS function or a Sass function. + class FunctionExpression : public InvocationExpression { + + // The namespace of the function being invoked, + // or `null` if it's invoked without a namespace. + ADD_CONSTREF(sass::string, ns); + + ADD_PROPERTY(IdxRef, fidx); + + // The name of the function being invoked. If this is + // interpolated, the function will be interpreted as plain + // CSS, even if it has the same name as a Sass function. + ADD_PROPERTY(InterpolationObj, name); + + public: + FunctionExpression(const SourceSpan& pstate, + Interpolation* name, + ArgumentInvocation* arguments, + const sass::string& ns = "") : + InvocationExpression(pstate, arguments), + ns_(ns), name_(name) + { + + } + + ATTACH_CRTP_PERFORM_METHODS(); + }; + + ///////////////////////////////////////////////////////////////////////////// + // Definitions for both mixins and functions. The two cases are distinguished + // by a type tag. + ///////////////////////////////////////////////////////////////////////////// + class CallableDeclaration : public ParentStatement { + // The name of this callable. + // This may be empty for callables without names. + ADD_CONSTREF(EnvString, name); + ADD_POINTER(IDXS*, idxs); + ADD_PROPERTY(IdxRef, fidx); + ADD_PROPERTY(IdxRef, cidx); + + // The comment immediately preceding this declaration. + ADD_PROPERTY(SilentCommentObj, comment); + // The declared arguments this callable accepts. + ADD_PROPERTY(ArgumentDeclarationObj, arguments); + public: + CallableDeclaration( + const SourceSpan& pstate, + const EnvString& name, + ArgumentDeclaration* arguments, + SilentComment* comment = nullptr, + Block* block = nullptr); + + // Stringify declarations etc. (dart) + virtual sass::string toString1() const = 0; + + ATTACH_ABSTRACT_CRTP_PERFORM_METHODS(); + }; + + class ContentBlock : + public CallableDeclaration { + public: + ContentBlock( + const SourceSpan& pstate, + ArgumentDeclaration* arguments = nullptr, + const sass::vector& children = {}); + sass::string toString1() const override final; + ATTACH_CRTP_PERFORM_METHODS(); + }; + + class FunctionRule final : + public CallableDeclaration { + public: + FunctionRule( + const SourceSpan& pstate, + const EnvString& name, + ArgumentDeclaration* arguments, + SilentComment* comment = nullptr, + Block* block = nullptr); + sass::string toString1() const override final; + ATTACH_CRTP_PERFORM_METHODS(); + }; + + class MixinRule final : + public CallableDeclaration { + ADD_CONSTREF(IdxRef, cidx); + public: + MixinRule( + const SourceSpan& pstate, + const sass::string& name, + ArgumentDeclaration* arguments, + SilentComment* comment = nullptr, + Block* block = nullptr); + sass::string toString1() const override final; + ATTACH_CRTP_PERFORM_METHODS(); + }; + + class IncludeRule final : public InvocationStatement { + + // The namespace of the mixin being invoked, or + // `null` if it's invoked without a namespace. + ADD_CONSTREF(sass::string, ns); + + // The name of the mixin being invoked. + ADD_CONSTREF(EnvString, name); + + // The block that will be invoked for [ContentRule]s in the mixin + // being invoked, or `null` if this doesn't pass a content block. + ADD_PROPERTY(ContentBlockObj, content); + + ADD_CONSTREF(IdxRef, midx); + + public: + + IncludeRule( + const SourceSpan& pstate, + const EnvString& name, + ArgumentInvocation* arguments, + const sass::string& ns = "", + ContentBlock* content = nullptr, + Block* block = nullptr); + + bool has_content() override final; + + ATTACH_CRTP_PERFORM_METHODS(); + }; + + /////////////////////////////////////////////////// + // The @content directive for mixin content blocks. + /////////////////////////////////////////////////// + class ContentRule final : public Statement { + ADD_PROPERTY(ArgumentInvocationObj, arguments); + public: + ContentRule(const SourceSpan& pstate, + ArgumentInvocation* arguments); + ATTACH_CRTP_PERFORM_METHODS() + }; + + //////////////////////////////////////////////////////////////////////////// + class ParenthesizedExpression final : public Expression { + ADD_PROPERTY(ExpressionObj, expression) + public: + ParenthesizedExpression(const SourceSpan& pstate, Expression* expression); + // ATTACH_CLONE_OPERATIONS(ParenthesizedExpression); + ATTACH_CRTP_PERFORM_METHODS(); + }; + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + // Arithmetic negation (logical negation is just an ordinary function call). + //////////////////////////////////////////////////////////////////////////// + class Unary_Expression final : public Expression { + public: + enum Type { PLUS, MINUS, NOT, SLASH }; + private: + ADD_PROPERTY(Type, optype) + ADD_PROPERTY(Expression_Obj, operand) + public: + Unary_Expression(const SourceSpan& pstate, Type t, Expression_Obj o); + // ATTACH_CLONE_OPERATIONS(Unary_Expression) + ATTACH_CRTP_PERFORM_METHODS() + }; + + // A Media Ruleset before it has been evaluated + // Could be already final or an interpolation + class MediaRule final : public ParentStatement { + ADD_PROPERTY(InterpolationObj, query) + public: + // The query that determines on which platforms the styles will be in effect. + // This is only parsed after the interpolation has been resolved. + MediaRule(const SourceSpan& pstate, InterpolationObj query, Block_Obj block = {}); + + bool bubbles() const override final { return true; }; + bool is_invisible() const override { return false; }; + // ATTACH_CLONE_OPERATIONS(MediaRule) + ATTACH_CRTP_PERFORM_METHODS() + }; + + ///////////////////////////////////////////////// + // A query for the `@at-root` rule. + ///////////////////////////////////////////////// + class AtRootQuery final : public AST_Node { + private: + // The names of the rules included or excluded by this query. There are + // two special names. "all" indicates that all rules are included or + // excluded, and "rule" indicates style rules are included or excluded. + ADD_PROPERTY(StringSet, names); + // Whether the query includes or excludes rules with the specified names. + ADD_PROPERTY(bool, include); + + public: + + AtRootQuery( + const SourceSpan& pstate, + const StringSet& names, + bool include); + + // Whether this includes or excludes *all* rules. + bool all() const; + + // Whether this includes or excludes style rules. + bool rule() const; + + // Whether this includes or excludes media rules. + bool media() const; + + // Returns the at-rule name for [node], or `null` if it's not an at-rule. + sass::string _nameFor(Statement* node) const; + + // Returns whether [this] excludes a node with the given [name]. + bool excludesName(const sass::string& name) const; + + // Returns whether [this] excludes [node]. + bool excludes(Statement* node) const; + + // Whether this excludes `@media` rules. + // Note that this takes [include] into account. + bool excludesMedia() const; + + // Whether this excludes style rules. + // Note that this takes [include] into account. + bool excludesStyleRules() const; + + // Parses an at-root query from [contents]. If passed, [url] + // is the name of the file from which [contents] comes. + // Throws a [SassFormatException] if parsing fails. + static AtRootQuery* parse( + const sass::string& contents, Context& ctx); + + // The default at-root query, which excludes only style rules. + // ToDo: check out how to make this static + static AtRootQuery* defaultQuery(const SourceSpan& pstate); + + // Only for debug purposes + sass::string toString() const; + + ATTACH_CRTP_PERFORM_METHODS() + }; + + /////////// + // At-root. + /////////// + class AtRootRule final : public ParentStatement { + ADD_PROPERTY(InterpolationObj, query); + ADD_POINTER(IDXS*, idxs); + public: + AtRootRule(SourceSpan&& pstate, InterpolationObj query = {}, Block_Obj b = {}); + // ATTACH_CLONE_OPERATIONS(CssAtRootRule) + ATTACH_CRTP_PERFORM_METHODS() + }; + + //////////////////////////////////////////////////////////// + // Individual argument objects for mixin and function calls. + //////////////////////////////////////////////////////////// + class Argument final : public Expression { + HASH_PROPERTY(Expression_Obj, value); + HASH_CONSTREF(EnvString, name); + ADD_PROPERTY(bool, is_rest_argument); + ADD_PROPERTY(bool, is_keyword_argument); + mutable size_t hash_; + public: + Argument(const SourceSpan& pstate, Expression_Obj val, + const EnvString& n, bool rest = false, bool keyword = false); + size_t hash() const override; + ATTACH_CLONE_OPERATIONS(Argument) + ATTACH_CRTP_PERFORM_METHODS() + }; + + ///////////////////////////////////////////////////////////////////////// + // Parameter lists -- in their own class to facilitate context-sensitive + // error checking (e.g., ensuring that all optional parameters follow all + // required parameters). + ///////////////////////////////////////////////////////////////////////// + typedef Value* (*SassFnSig)(FN_PROTOTYPE2); + typedef std::pair SassFnPair; + typedef sass::vector SassFnPairs; + + class Callable : public SassNode { + public: + Callable(const SourceSpan& pstate); + virtual Value* execute(Eval& eval, ArgumentInvocation* arguments, const SourceSpan& pstate) = 0; + virtual bool operator== (const Callable& rhs) const = 0; + ATTACH_CRTP_PERFORM_METHODS() + }; + + class UserDefinedCallable : public Callable { + // Name of this callable (used for reporting) + ADD_CONSTREF(EnvString, name); + // The declaration (parameters this function takes). + ADD_PROPERTY(CallableDeclarationObj, declaration); + // The environment in which this callable was declared. + ADD_POINTER(EnvSnapshot*, snapshot); + public: + UserDefinedCallable( + const SourceSpan& pstate, + const EnvString& name, + CallableDeclarationObj declaration, + EnvSnapshot* snapshot); + Value* execute(Eval& eval, ArgumentInvocation* arguments, const SourceSpan& pstate) override final; + bool operator== (const Callable& rhs) const override final; + ATTACH_CRTP_PERFORM_METHODS() + }; + + class PlainCssCallable : public Callable { + ADD_CONSTREF(sass::string, name); + public: + PlainCssCallable(const SourceSpan& pstate, const sass::string& name); + Value* execute(Eval& eval, ArgumentInvocation* arguments, const SourceSpan& pstate) override final; + bool operator== (const Callable& rhs) const override final; + ATTACH_CRTP_PERFORM_METHODS() + }; + + class BuiltInCallable : public Callable { + + // The function name + ADD_CONSTREF(EnvString, name); + + ADD_PROPERTY(ArgumentDeclarationObj, parameters); + + ADD_CONSTREF(SassFnPair, function); + + public: + + Value* execute(Eval& eval, ArgumentInvocation* arguments, const SourceSpan& pstate) override final; + + // Value* execute(ArgumentInvocation* arguments) { + // // return callback(arguments); + // return nullptr; + // } + + // Creates a callable with a single [arguments] declaration + // and a single [callback]. The argument declaration is parsed + // from [arguments], which should not include parentheses. + // Throws a [SassFormatException] if parsing fails. + BuiltInCallable( + const EnvString& name, + ArgumentDeclaration* parameters, + const SassFnSig& callback); + + virtual const SassFnPair& callbackFor( + size_t positional, + const KeywordMap& names); + + bool operator== (const Callable& rhs) const override final; + + ATTACH_CRTP_PERFORM_METHODS() + }; + + class BuiltInCallables : public Callable { + + // The function name + ADD_CONSTREF(EnvString, name); + + // The overloads declared for this callable. + ADD_PROPERTY(SassFnPairs, overloads); + + public: + + Value* execute(Eval& eval, ArgumentInvocation* arguments, const SourceSpan& pstate) override final; + + // Value* execute(ArgumentInvocation* arguments) { + // // return callback(arguments); + // return nullptr; + // } + + // Creates a callable with multiple implementations. Each + // key/value pair in [overloads] defines the argument declaration + // for the overload (which should not include parentheses), and + // the callback to execute if that argument declaration matches. + // Throws a [SassFormatException] if parsing fails. + BuiltInCallables( + const EnvString& name, + const SassFnPairs& overloads); + + const SassFnPair& callbackFor( + size_t positional, + const KeywordMap& names); // override final; + + bool operator== (const Callable& rhs) const override final; + + ATTACH_CRTP_PERFORM_METHODS() + }; + + class ExternalCallable : public Callable { + + // The function name + ADD_CONSTREF(sass::string, name); + + ADD_PROPERTY(ArgumentDeclarationObj, declaration); + + ADD_PROPERTY(Sass_Function_Entry, function); + + ADD_POINTER(IDXS*, idxs); + + public: + + ExternalCallable( + const sass::string& name, + ArgumentDeclaration* parameters, + Sass_Function_Entry function); + Value* execute(Eval& eval, ArgumentInvocation* arguments, const SourceSpan& pstate) override final; + bool operator== (const Callable& rhs) const override final; + + ATTACH_CRTP_PERFORM_METHODS() + }; + + +} + +#include "ast_css.hpp" +#include "ast_values.hpp" +#include "ast_supports.hpp" +#include "ast_selectors.hpp" + +#ifdef __clang__ + +// #pragma clang diagnostic pop +// #pragma clang diagnostic push + +#endif + +#endif diff --git a/src/ast2c.cpp b/src/ast2c.cpp deleted file mode 100644 index f167b7ea77..0000000000 --- a/src/ast2c.cpp +++ /dev/null @@ -1,80 +0,0 @@ -// sass.hpp must go before all system headers to get the -// __EXTENSIONS__ fix on Solaris. -#include "sass.hpp" - -#include "ast2c.hpp" -#include "ast.hpp" - -namespace Sass { - - union Sass_Value* AST2C::operator()(Boolean* b) - { return sass_make_boolean(b->value()); } - - union Sass_Value* AST2C::operator()(Number* n) - { return sass_make_number(n->value(), n->unit().c_str()); } - - union Sass_Value* AST2C::operator()(Custom_Warning* w) - { return sass_make_warning(w->message().c_str()); } - - union Sass_Value* AST2C::operator()(Custom_Error* e) - { return sass_make_error(e->message().c_str()); } - - union Sass_Value* AST2C::operator()(Color_RGBA* c) - { return sass_make_color(c->r(), c->g(), c->b(), c->a()); } - - union Sass_Value* AST2C::operator()(Color_HSLA* c) - { - Color_RGBA_Obj rgba = c->copyAsRGBA(); - return operator()(rgba.ptr()); - } - - union Sass_Value* AST2C::operator()(String_Constant* s) - { - if (s->quote_mark()) { - return sass_make_qstring(s->value().c_str()); - } else { - return sass_make_string(s->value().c_str()); - } - } - - union Sass_Value* AST2C::operator()(String_Quoted* s) - { return sass_make_qstring(s->value().c_str()); } - - union Sass_Value* AST2C::operator()(List* l) - { - union Sass_Value* v = sass_make_list(l->length(), l->separator(), l->is_bracketed()); - for (size_t i = 0, L = l->length(); i < L; ++i) { - sass_list_set_value(v, i, (*l)[i]->perform(this)); - } - return v; - } - - union Sass_Value* AST2C::operator()(Map* m) - { - union Sass_Value* v = sass_make_map(m->length()); - int i = 0; - for (auto key : m->keys()) { - sass_map_set_key(v, i, key->perform(this)); - sass_map_set_value(v, i, m->at(key)->perform(this)); - i++; - } - return v; - } - - union Sass_Value* AST2C::operator()(Arguments* a) - { - union Sass_Value* v = sass_make_list(a->length(), SASS_COMMA, false); - for (size_t i = 0, L = a->length(); i < L; ++i) { - sass_list_set_value(v, i, (*a)[i]->perform(this)); - } - return v; - } - - union Sass_Value* AST2C::operator()(Argument* a) - { return a->value()->perform(this); } - - // not strictly necessary because of the fallback - union Sass_Value* AST2C::operator()(Null* n) - { return sass_make_null(); } - -}; diff --git a/src/ast2c.hpp b/src/ast2c.hpp deleted file mode 100644 index cd99a17c31..0000000000 --- a/src/ast2c.hpp +++ /dev/null @@ -1,39 +0,0 @@ -#ifndef SASS_AST2C_H -#define SASS_AST2C_H - -#include "ast_fwd_decl.hpp" -#include "operation.hpp" -#include "sass/values.h" - -namespace Sass { - - class AST2C : public Operation_CRTP { - - public: - - AST2C() { } - ~AST2C() { } - - union Sass_Value* operator()(Boolean*); - union Sass_Value* operator()(Number*); - union Sass_Value* operator()(Color_RGBA*); - union Sass_Value* operator()(Color_HSLA*); - union Sass_Value* operator()(String_Constant*); - union Sass_Value* operator()(String_Quoted*); - union Sass_Value* operator()(Custom_Warning*); - union Sass_Value* operator()(Custom_Error*); - union Sass_Value* operator()(List*); - union Sass_Value* operator()(Map*); - union Sass_Value* operator()(Null*); - union Sass_Value* operator()(Arguments*); - union Sass_Value* operator()(Argument*); - - // return sass error if type is not supported - union Sass_Value* fallback(AST_Node* x) - { return sass_make_error("unknown type for C-API"); } - - }; - -} - -#endif diff --git a/src/ast_callable.hpp b/src/ast_callable.hpp new file mode 100644 index 0000000000..0f9e455f6a --- /dev/null +++ b/src/ast_callable.hpp @@ -0,0 +1,267 @@ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#ifndef SASS_AST_CALLABLE_HPP +#define SASS_AST_CALLABLE_HPP + +// sass.hpp must go before all system headers +// to get the __EXTENSIONS__ fix on Solaris. +#include "capi_sass.hpp" + +#include "fn_utils.hpp" +#include "ast_nodes.hpp" +#include "environment_key.hpp" +#include "environment_stack.hpp" + +namespace Sass { + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + typedef Value* (*SassFnSig)(FN_PROTOTYPE); + typedef std::pair SassFnPair; + typedef sass::vector SassFnPairs; + + ///////////////////////////////////////////////////////////////////////// + // Base class for everything that can be called on demand. + ///////////////////////////////////////////////////////////////////////// + + class Callable : public AstNode + { + + protected: + + // Hash is only calculated once and afterwards the value + // must not be mutated, which is the case with how sass + // works, although we must be a bit careful not to alter + // any value that has already been added to a set or map. + // Must create a copy if you need to alter such an object. + mutable size_t hash_; + + public: + + // Value constructor + Callable(const SourceSpan& pstate); + + // The main entry point to execute the function + // Must be implemented in each specialization + virtual Value* execute(Eval& eval, + CallableArguments* arguments, + const SourceSpan& pstate) = 0; + + // Return name of this callable/function + virtual const sass::string& name() const = 0; + + // Equality comparator (needed for `get-function` value) + virtual bool operator==(const Callable& rhs) const = 0; + + // Check if call is considered internal + // True only for certain built-ins + virtual bool isInternal() const { + return false; + } + + // Implement interface for base Value class + virtual size_t hash() const = 0; + + // Declare up-casting methods + DECLARE_ISA_CASTER(BuiltInCallable); + DECLARE_ISA_CASTER(BuiltInCallables); + DECLARE_ISA_CASTER(UserDefinedCallable); + DECLARE_ISA_CASTER(ExternalCallable); + DECLARE_ISA_CASTER(PlainCssCallable); + }; + + ///////////////////////////////////////////////////////////////////////// + // Individual argument object for function signatures. + ///////////////////////////////////////////////////////////////////////// + + class Argument final : public AstNode + { + private: + + ADD_CONSTREF(EnvKey, name); + ADD_CONSTREF(ExpressionObj, defval); + ADD_CONSTREF(bool, is_rest_argument); + ADD_CONSTREF(bool, is_keyword_argument); + + public: + + // Value constructor + Argument(const SourceSpan& pstate, + const EnvKey& name, + ExpressionObj defval, + bool is_rest_argument = false, + bool is_keyword_argument = false); + + }; + + ////////////////////////////////////////////////////////////////////// + // Object for the function signature holding which parameters a + // callable can have or expects, with optional rest arguments. + ////////////////////////////////////////////////////////////////////// + + class CallableSignature final : public AstNode + { + + protected: + + // Hash is only calculated once and afterwards the value + // must not be mutated, which is the case with how sass + // works, although we must be a bit careful not to alter + // any value that has already been added to a set or map. + // Must create a copy if you need to alter such an object. + mutable size_t hash_; + + private: + + // The arguments that are taken. + ADD_CONSTREF(sass::vector, arguments); + + // The name of the rest argument (as in `$args...`), + ADD_CONSTREF(EnvKey, restArg); + + // This is only used for debugging + ADD_CONSTREF(size_t, maxArgs); + + public: + + // Value constructor + CallableSignature(SourceSpan&& pstate, + sass::vector&& arguments = {}, + EnvKey&& restArg = {}); + + // Value constructor + CallableSignature(const SourceSpan& pstate, + sass::vector&& arguments = {}, + EnvKey&& restArg = {}); + + // Checks if signature is void + bool isEmpty() const { + return arguments_.empty() + && restArg_.empty(); + } + + // Parse `source` into signature + static CallableSignature* parse( + Compiler& context, SourceData* source); + + // Throws a [SassScriptException] if [positional] and + // [names] aren't valid for this argument declaration. + void verify( + size_t positional, + const ValueFlatMap& names, + const SourceSpan& pstate, + const BackTraces& traces) const; + + // Returns whether [positional] and [names] + // are valid for this argument declaration. + bool matches(const ArgumentResults& evaluated) const; + + size_t hash() const; + + }; + + ///////////////////////////////////////////////////////////////////////// + // Object for the actual function arguments to pass to the function + // invocation. It must be valid in regard to the callable signature + // of the invoked function (will throw an error otherwise). + ///////////////////////////////////////////////////////////////////////// + + class CallableArguments final : public AstNode + { + private: + + // The arguments passed by position. + ADD_CONSTREF(ExpressionVector, positional); + + // The arguments passed by name. + ADD_CONSTREF(ExpressionFlatMap, named); + + // Optional rest argument (as in `$args...`). + // Supports only one rest arg and it must be last. + // ToDo: explain difference between restArg and kwdRest. + ADD_CONSTREF(ExpressionObj, restArg); + + // The second rest argument, which is expected to only contain a keyword map. + // This can be an already evaluated Map (via call) or a MapExpression. + // So we must guarantee that this evaluates to a real Map value. + ADD_CONSTREF(ExpressionObj, kwdRest); + + public: + + // Value move constructor + CallableArguments(SourceSpan&& pstate, + ExpressionVector&& positional, + ExpressionFlatMap&& named, + Expression* restArgs = nullptr, + Expression* kwdRest = nullptr); + + // Partial value move constructor + CallableArguments(const SourceSpan& pstate, + ExpressionVector&& positional, + ExpressionFlatMap&& named, + Expression* restArgs = nullptr, + Expression* kwdRest = nullptr); + + // Returns whether this invocation passes no arguments. + bool isEmpty() const; + + }; + + ///////////////////////////////////////////////////////////////////////// + // The result of evaluating arguments to a function or mixin. + // It's basically the same as `CallableArguments` but with all + // based values already evaluated in order to check compliance + // with the expected callable signature. + ///////////////////////////////////////////////////////////////////////// + + class ArgumentResults final { + + // Arguments passed by position. + ADD_REF(ValueVector, positional); + + // Arguments passed by name. + // A list implementation is often more efficient + // We don't expect any function to have many arguments + // Normally trade-off starts around 8 items in the list + ADD_REF(ValueFlatMap, named); + + // Separator used for rest argument list, if any. + ADD_CONSTREF(SassSeparator, separator); + + public: + + // Value constructor + ArgumentResults() : + separator_(SASS_UNDEF) + {}; + + // Value move constructor + ArgumentResults( + ValueVector&& positional, + ValueFlatMap&& named, + SassSeparator separator); + + // Move constructor + ArgumentResults( + ArgumentResults&& other) noexcept; + + // Move assignment operator + ArgumentResults& operator=( + ArgumentResults&& other) noexcept; + + // Clear results + void clear() { + named_.clear(); + positional_.clear(); + } + + }; + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + +} + +#endif diff --git a/src/ast_callables.cpp b/src/ast_callables.cpp new file mode 100644 index 0000000000..c384af7c8c --- /dev/null +++ b/src/ast_callables.cpp @@ -0,0 +1,500 @@ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#include "ast_callables.hpp" + +#include "exceptions.hpp" +#include "parser_scss.hpp" +#include "compiler.hpp" +#include "eval.hpp" + +namespace Sass { + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + Callable::Callable( + const SourceSpan& pstate) : + AstNode(pstate), + hash_(0) + {} + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // This should be thread-safe + static std::hash ptrHasher; + static std::hash boolHasher; + //static std::hash doubleHasher; + static std::hash fnHasher; + //static std::hash sizetHasher; + static std::hash stringHasher; + static std::hash lambdaHasher; + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + BuiltInCallable::BuiltInCallable( + const EnvKey& envkey, + CallableSignature* signature, + const SassFnSig& callback, + bool isInternal) : + Callable(SourceSpan::internal("[BUILTIN]")), + envkey_(envkey), + // Create a single entry in overloaded function + function_(SassFnPair{ signature, callback }), + isInternalFn_(isInternal), + acceptsContent_(false) + {} + + // Return callback with matching signature + const SassFnPair& BuiltInCallable::callbackFor( + const ArgumentResults& evaluated) + { + return function_; + } + + // Equality comparator (needed for `get-function` value) + // ToDo: doesn't seem to be one hundert percent correct? + bool BuiltInCallable::operator==(const Callable& rhs) const + { + if (const BuiltInCallable* builtin = rhs.isaBuiltInCallable()) { + return envkey_ == builtin->envkey_ && + function_.first == builtin->function_.first && + function_.second == builtin->function_.second; + } + return false; + } + + size_t BuiltInCallable::hash() const + { + if (hash_ == 0) { + hash_start(hash_, typeid(BuiltInCallable).hash_code()); + hash_combine(hash_, stringHasher(envkey_.norm())); + hash_combine(hash_, fnHasher(function_.second)); + hash_combine(hash_, function_.first->hash()); + } + return hash_; + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + BuiltInCallables::BuiltInCallables( + const EnvKey& envkey, + const SassFnPairs& overloads) : + Callable(SourceSpan::internal("[BUILTINS]")), + envkey_(envkey), + overloads_(overloads) + { + size_t size = 0; + for (auto fn : overloads) { + size = std::max(size, + fn.first->maxArgs()); + } + for (auto fn : overloads) { + fn.first->maxArgs(size); + } + } + + // Return callback with matching signature + const SassFnPair& BuiltInCallables::callbackFor( + const ArgumentResults& evaluated) + { + for (SassFnPair& pair : overloads_) { + if (pair.first->matches(evaluated)) { + return pair; + } + } + return overloads_.back(); + } + + // Equality comparator (needed for `get-function` value) + bool BuiltInCallables::operator==(const Callable& rhs) const + { + if (const BuiltInCallables* builtin = rhs.isaBuiltInCallables()) { + if (!(envkey_ == builtin->envkey_)) return false; + return overloads_ == builtin->overloads_; + } + return false; + } + + size_t BuiltInCallables::hash() const + { + if (hash_ == 0) { + hash_start(hash_, typeid(BuiltInCallables).hash_code()); + hash_combine(hash_, stringHasher(envkey_.norm())); + for (const auto& pair : overloads_) { + hash_combine(hash_, fnHasher(pair.second)); + hash_combine(hash_, pair.first->hash()); + } + } + return hash_; + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + UserDefinedCallable::UserDefinedCallable( + const SourceSpan& pstate, + const EnvKey& envkey, + CallableDeclarationObj declaration, + UserDefinedCallable* content) : + Callable(pstate), + envkey_(envkey), + declaration_(declaration), + content_(content) + {} + + // Equality comparator (needed for `get-function` value) + bool UserDefinedCallable::operator==(const Callable& rhs) const + { + if (const UserDefinedCallable* builtin = rhs.isaUserDefinedCallable()) { + return envkey_ == builtin->envkey_ && + // Must use pointer equality here + declaration_ == builtin->declaration_; + } + return false; + } + + size_t UserDefinedCallable::hash() const + { + if (hash_ == 0) { + hash_start(hash_, typeid(UserDefinedCallable).hash_code()); + hash_combine(hash_, stringHasher(envkey_.norm())); + hash_combine(hash_, ptrHasher(declaration_.ptr())); + } + return hash_; + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + ExternalCallable::ExternalCallable( + const EnvKey& fname, + CallableSignature* parameters, + SassFunctionLambda lambda) : + Callable(SourceSpan::internal("[EXTERNAL]")), + envkey_(fname), + declaration_(parameters), + lambda_(lambda), + cookie_(nullptr) + {} + + // Equality comparator (needed for `get-function` value) + bool ExternalCallable::operator==(const Callable& rhs) const + { + if (const ExternalCallable* builtin = rhs.isaExternalCallable()) { + return envkey_ == builtin->envkey_ && + lambda_ == builtin->lambda_; + } + return false; + } + + size_t ExternalCallable::hash() const + { + if (hash_ == 0) { + hash_start(hash_, typeid(ExternalCallable).hash_code()); + hash_combine(hash_, stringHasher(envkey_.norm())); + hash_combine(hash_, ptrHasher(declaration_)); + hash_combine(hash_, lambdaHasher(lambda_)); + hash_combine(hash_, ptrHasher(cookie_)); + } + return hash_; + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + Argument::Argument(const SourceSpan& pstate, + const EnvKey& name, + ExpressionObj defval, + bool is_rest_argument, + bool is_keyword_argument) : + AstNode(pstate), + name_(name), + defval_(defval), + is_rest_argument_(is_rest_argument), + is_keyword_argument_(is_keyword_argument) + {} + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + CallableSignature::CallableSignature( + SourceSpan&& pstate, + sass::vector&& arguments, + EnvKey&& restArg) : + hash_(0), // calc on demand + AstNode(std::move(pstate)), + arguments_(std::move(arguments)), + restArg_(std::move(restArg)), + maxArgs_(arguments_.size()) + { + if (!restArg_.empty()) { + maxArgs_ += 1; + } + } + + CallableSignature::CallableSignature( + const SourceSpan& pstate, + sass::vector&& arguments, + EnvKey&& restArg) : + AstNode(pstate), + hash_(0), // calc on demand + arguments_(std::move(arguments)), + restArg_(std::move(restArg)), + maxArgs_(arguments_.size()) + { + if (!restArg_.empty()) { + maxArgs_ += 1; + } + } + + // Parse `source` into signature + CallableSignature* CallableSignature::parse( + Compiler& context, SourceData* source) + { + ScssParser parser(context, source); + return parser.parseArgumentDeclaration(); + } + + // Throws a [SassScriptException] if [positional] and + // [names] aren't valid for this argument declaration. + void CallableSignature::verify( + size_t positional, + const ValueFlatMap& names, + const SourceSpan& pstate, + const BackTraces& traces) const + { + + size_t i = 0; + size_t namedUsed = 0; + size_t iL = arguments_.size(); + while (i < std::min(positional, iL)) { + if (names.count(arguments_[i]->name()) == 1) { + throw Exception::RuntimeException(traces, + "Argument $" + arguments_[i]->name().orig() + + " name was passed both by position and by name."); + } + i++; + } + while (i < iL) { + if (names.count(arguments_[i]->name()) == 1) { + namedUsed++; + } + else if (arguments_[i]->defval() == nullptr) { + throw Exception::RuntimeException(traces, + "Missing element $" + arguments_[i]->name().orig() + "."); + } + i++; + } + + if (!restArg_.empty()) return; + + if (positional > arguments_.size()) { + sass::sstream strm; + strm << "Only " << arguments_.size() << " "; // " positional "; + strm << pluralize("argument", arguments_.size()); + strm << " allowed, but " << positional << " "; + strm << pluralize("was", positional, "were"); + strm << " passed."; + throw Exception::RuntimeException( + traces, strm.str()); + } + + if (namedUsed < names.size()) { + ValueFlatMap unknownNames(names); + for (Argument* arg : arguments_) { + unknownNames.erase(arg->name()); + } + throw Exception::RuntimeException( + traces, "No argument named $" + + toSentence(getKeyVector(unknownNames), "or") + "."); + } + + } + // EO verify + + // Returns whether [positional] and [names] + // are valid for this argument declaration. + bool CallableSignature::matches( + const ArgumentResults& evaluated) const + { + size_t namedUsed = 0; Argument* argument; + for (size_t i = 0, iL = arguments_.size(); i < iL; i++) { + argument = arguments_[i]; + if (i < evaluated.positional().size()) { + if (evaluated.named().count(argument->name()) == 1) { + return false; + } + } + else if (evaluated.named().count(argument->name()) == 1) { + namedUsed++; + } + else if (argument->defval().isNull()) { + return false; + } + } + if (!restArg_.empty()) return true; + if (evaluated.positional().size() > arguments_.size()) return false; + if (namedUsed < evaluated.named().size()) return false; + return true; + } + + size_t CallableSignature::hash() const + { + if (hash_ == 0) { + hash_start(hash_, typeid(CallableSignature).hash_code()); + hash_combine(hash_, stringHasher(restArg_.norm())); + for (const Argument* param : arguments_) { + if (param == nullptr) continue; + hash_combine(hash_, stringHasher(param->name().norm())); + hash_combine(hash_, boolHasher(param->is_rest_argument())); + hash_combine(hash_, boolHasher(param->is_keyword_argument())); + } + } + return hash_; + } + // EO matches + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + CallableArguments::CallableArguments( + const SourceSpan& pstate, + ExpressionVector&& positional, + ExpressionFlatMap&& named, + Expression* restArg, + Expression* kwdRest) : + AstNode(pstate), + positional_(std::move(positional)), + named_(std::move(named)), + restArg_(restArg), + kwdRest_(kwdRest) + {} + + CallableArguments::CallableArguments( + SourceSpan&& pstate, + ExpressionVector&& positional, + ExpressionFlatMap&& named, + Expression* restArg, + Expression* kwdRest) : + AstNode(std::move(pstate)), + positional_(std::move(positional)), + named_(std::move(named)), + restArg_(restArg), + kwdRest_(kwdRest) + {} + + // Returns whether this invocation passes no arguments. + bool CallableArguments::isEmpty() const + { + return positional_.empty() + && named_.empty() + && restArg_.isNull(); + } + // EO isEmpty + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + ArgumentResults::ArgumentResults( + ValueVector&& positional, + ValueFlatMap&& named, + SassSeparator separator) : + positional_(std::move(positional)), + named_(std::move(named)), + separator_(separator) + {} + + ArgumentResults::ArgumentResults( + ArgumentResults&& other) noexcept : + positional_(std::move(other.positional_)), + named_(std::move(other.named_)), + separator_(other.separator_) + {} + + ArgumentResults& ArgumentResults::operator=( + ArgumentResults&& other) noexcept + { + positional_ = std::move(other.positional_); + named_ = std::move(other.named_); + separator_ = other.separator_; + return *this; + } + + ///////////////////////////////////////////////////////////////////////// + // Implement the execute dispatch to evaluator + ///////////////////////////////////////////////////////////////////////// + + Value* BuiltInCallable::execute(Eval& eval, CallableArguments* arguments, const SourceSpan& pstate) + { + return eval.execute(this, arguments, pstate); + } + + Value* BuiltInCallables::execute(Eval& eval, CallableArguments* arguments, const SourceSpan& pstate) + { + return eval.execute(this, arguments, pstate); + } + + Value* UserDefinedCallable::execute(Eval& eval, CallableArguments* arguments, const SourceSpan& pstate) + { + return eval.execute(this, arguments, pstate); + } + + Value* ExternalCallable::execute(Eval& eval, CallableArguments* arguments, const SourceSpan& pstate) + { + return eval.execute(this, arguments, pstate); + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + PlainCssCallable::PlainCssCallable(const SourceSpan& pstate, const EnvKey& fname) : + Callable(pstate), + envkey_(fname) + { + } + + Value* PlainCssCallable::execute(Eval& eval, CallableArguments* arguments, const SourceSpan& pstate) + { + + if (!arguments->named().empty() || arguments->kwdRest() != nullptr) { + throw Exception::RuntimeException(eval.compiler, + "Plain CSS functions don't support keyword arguments."); + } + + sass::string text(envkey_.orig()); + text += "("; + bool joiner = false; + for (auto arg : arguments->positional()) { + if (joiner) text += ", "; + text += arg->toString(); + joiner = true; + } + text += ")"; + return SASS_MEMORY_NEW(String, pstate, std::move(text)); + } + + bool PlainCssCallable::operator==(const Callable& rhs) const + { + if (const PlainCssCallable* builtin = rhs.isaPlainCssCallable()) { + return envkey_ == builtin->envkey_; + } + return false; + } + + size_t PlainCssCallable::hash() const + { + if (hash_ == 0) { + hash_start(hash_, typeid(PlainCssCallable).hash_code()); + hash_combine(hash_, stringHasher(envkey_.norm())); + } + return hash_; + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + +} diff --git a/src/ast_callables.hpp b/src/ast_callables.hpp new file mode 100644 index 0000000000..c0dbfc14fa --- /dev/null +++ b/src/ast_callables.hpp @@ -0,0 +1,247 @@ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +// This file was deliberately split from `ast_callable.hpp`. It contains +// all the specialized implementations for callables. These often need +// access to higher level classes, which poses include dependency issues +// if everything would be defined in a single header file. +/*****************************************************************************/ +#ifndef SASS_AST_CALLABLES_HPP +#define SASS_AST_CALLABLES_HPP + +// sass.hpp must go before all system headers +// to get the __EXTENSIONS__ fix on Solaris. +#include "capi_sass.hpp" + +#include "ast_nodes.hpp" +#include "ast_callable.hpp" +#include "ast_statements.hpp" +#include "capi_function.hpp" +#include "environment_key.hpp" +#include "environment_stack.hpp" + +namespace Sass { + + ///////////////////////////////////////////////////////////////////////// + // Internal callables provided by LibSass itself. + ///////////////////////////////////////////////////////////////////////// + + class BuiltInCallable final : public Callable + { + private: + + // Name of this callable/function + ADD_CONSTREF(EnvKey, envkey); + + // Pair of signature and callback + ADD_CONSTREF(SassFnPair, function); + + // Some functions are internal only + ADD_CONSTREF(bool, isInternalFn); + + // Some mixins accept content blocks + ADD_CONSTREF(bool, acceptsContent); + + public: + + // Creates a callable with a single [arguments] declaration + // and a single [callback]. The argument declaration is parsed + // from [arguments], which should not include parentheses. + // Throws a [SassFormatException] if parsing fails. + BuiltInCallable( + const EnvKey& fname, + CallableSignature* signature, + const SassFnSig& callback, + bool isInternal = false); + + // Return callback with matching signature + const SassFnPair& callbackFor( + const ArgumentResults& evaluated); + + // The main entry point to execute the function (implemented in each specialization) + Value* execute(Eval& eval, CallableArguments* arguments, const SourceSpan& pstate) override final; + + // Return the function name + const sass::string& name() const override final { return envkey_.norm(); } + + // Equality comparator (needed for `get-function` value) + bool operator==(const Callable& rhs) const override final; + + size_t hash() const override final; + + // Check if call is considered internal + bool isInternal() const override final { + return isInternalFn_; + } + + // Define isaBuiltInCallable up-cast function + IMPLEMENT_ISA_CASTER(BuiltInCallable); + }; + + ///////////////////////////////////////////////////////////////////////// + // Internal callable with multiple signatures to choose from. + ///////////////////////////////////////////////////////////////////////// + + class BuiltInCallables final : public Callable + { + private: + + // Name of this callable/function + ADD_CONSTREF(EnvKey, envkey); + + // The overloads declared for this callable. + ADD_CONSTREF(SassFnPairs, overloads); + + public: + + // Creates a callable with multiple implementations. Each + // key/value pair in [overloads] defines the argument declaration + // for the overload (which should not include parentheses), and + // the callback to execute if that argument declaration matches. + // Throws a [SassFormatException] if parsing fails. + BuiltInCallables( + const EnvKey& envkey, + const SassFnPairs& overloads); + + // Return callback with matching signature + const SassFnPair& callbackFor( + const ArgumentResults& evaluated); + + // The main entry point to execute the function (implemented in each specialization) + Value* execute(Eval& eval, CallableArguments* arguments, const SourceSpan& pstate) override final; + + // Return the function name + const sass::string& name() const override final { return envkey_.norm(); } + + // Equality comparator (needed for `get-function` value) + bool operator==(const Callable& rhs) const override final; + + size_t hash() const override final; + + // Define isaBuiltInCallables up-cast function + IMPLEMENT_ISA_CASTER(BuiltInCallables); + }; + + ///////////////////////////////////////////////////////////////////////// + // User defined callable from sass code (functions and mixins) + ///////////////////////////////////////////////////////////////////////// + class UserDefinedCallable final : public Callable + { + private: + + // Name of this callable (used for reporting) + ADD_CONSTREF(EnvKey, envkey); + + // The declaration (parameters this callable takes). + ADD_CONSTREF(CallableDeclarationObj, declaration); + + // Content blocks passed to includes need to preserve + // the previous content block. Could have been implemented + // with a stack vector, but we remember it here instead. + ADD_PROPERTY(UserDefinedCallable*, content); + + public: + + // Value constructor + UserDefinedCallable( + const SourceSpan& pstate, + const EnvKey& fname, + CallableDeclarationObj declaration, + UserDefinedCallable* content = nullptr); + + // The main entry point to execute the function (implemented in each specialization) + Value* execute(Eval& eval, CallableArguments* arguments, const SourceSpan& pstate) override final; + + // Return the function name + const sass::string& name() const override final { return envkey_.norm(); } + + // Equality comparator (needed for `get-function` value) + bool operator==(const Callable& rhs) const override final; + + size_t hash() const override final; + + // Define isaUserDefinedCallable up-cast function + IMPLEMENT_ISA_CASTER(UserDefinedCallable); + }; + + ///////////////////////////////////////////////////////////////////////// + // External callable defined on the C-API side. + ///////////////////////////////////////////////////////////////////////// + + class ExternalCallable final : public Callable + { + private: + + // Name of this callable (used for reporting) + ADD_CONSTREF(EnvKey, envkey); + + // The declaration (parameters this function takes). + ADD_CONSTREF(CallableSignatureObj, declaration); + + // The attached external callback reference + ADD_CONSTREF(SassFunctionLambda, lambda); + + // The attached external data cookie + ADD_PROPERTY(void*, cookie); + + public: + + // Value constructor + ExternalCallable( + const EnvKey& fname, + CallableSignature* parameters, + SassFunctionLambda function); + + // The main entry point to execute the function (implemented in each specialization) + Value* execute(Eval& eval, CallableArguments* arguments, const SourceSpan& pstate) override final; + + // Return the function name + const sass::string& name() const override final { return envkey_.norm(); } + + // Equality comparator (needed for `get-function` value) + bool operator==(const Callable& rhs) const override final; + + size_t hash() const override final; + + // Define isaExternalCallable up-cast function + IMPLEMENT_ISA_CASTER(ExternalCallable); + }; + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + class PlainCssCallable final : public Callable + { + + // Name of this callable (used for reporting) + ADD_CONSTREF(EnvKey, envkey); + + public: + + // Value constructor + PlainCssCallable( + const SourceSpan& pstate, + const EnvKey& fname); + + // The main entry point to execute the function (implemented in each specialization) + Value* execute(Eval& eval, CallableArguments* arguments, const SourceSpan& pstate) override final; + + // Return the function name + const sass::string& name() const override final { return envkey_.norm(); } + + // Equality comparator (needed for `get-function` value) + bool operator==(const Callable& rhs) const override final; + + size_t hash() const override final; + + // Define isaExternalCallable up-cast function + IMPLEMENT_ISA_CASTER(PlainCssCallable); + + }; + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + +} + +#endif diff --git a/src/ast_containers.hpp b/src/ast_containers.hpp new file mode 100644 index 0000000000..71806dbf2e --- /dev/null +++ b/src/ast_containers.hpp @@ -0,0 +1,475 @@ +#ifndef SASS_AST_CONTAINERS_H +#define SASS_AST_CONTAINERS_H + +#include "hashing.hpp" +#include "ast_helpers.hpp" + +namespace Sass { + + ///////////////////////////////////////////////////////////////////////// + // Base class/container for AST nodes that should behave like vectors. + ///////////////////////////////////////////////////////////////////////// + + template + class Vectorized { + + protected: + + typedef SharedPtr T; + typedef Vectorized Klass; + + // The main underlying container + sass::vector elements_; + + // Hash is only calculated once and afterwards the value + // must not be mutated, which is the case with how sass + // works, although we must be a bit careful not to alter + // any value that has already been added to a set or map. + // Must create a copy if you need to alter such an object. + mutable size_t hash_ = 0; + + public: + + // Reserve constructor + Vectorized(size_t s = 0) + { + elements_.reserve(s); + } + + // Copy constructor from other Vectorized + Vectorized(const Vectorized* vec, bool childless = false) + { + if (!childless) { + hash_ = vec->hash_; + elements_ = vec->elements_; + } + } + + // Copy constructor from other base vector + Vectorized(const Vectorized& vec, bool childless = false) + { + if (!childless) { + hash_ = vec.hash_; + elements_ = vec.elements_; + } + } + + // Copy constructor from other base vector + Vectorized(const sass::vector& vec, bool childless = false) + { + if (!childless) { + elements_ = vec; + } + } + + // Move constructor from other base Vectorized + Vectorized(Vectorized&& vec, bool childless = false) + { + if (!childless) { + hash_ = vec.hash_; + elements_ = std::move(vec.elements_); + } + } + + // Move constructor from other base vector + Vectorized(sass::vector&& vec, bool childless = false) + { + if (!childless) { + elements_ = std::move(vec); + } + } + + // Copy constructor from other base Vectorized + Vectorized& operator=(const Vectorized& other) + { + this->hash_ = other.hash_; + this->elements_ = other.elements_; + return *this; + } + + // Copy constructor from other base vector + Vectorized& operator=(const sass::vector& other) + { + this->hash_ = 0; + this->elements_ = other; + return *this; + } + + // Move constructor from other base Vectorized + Vectorized& operator=(Vectorized&& other) + { + this->hash_ = other.hash_; + this->elements_ = std::move(other.elements_); + return *this; + } + + // Move constructor from other base vector + Vectorized& operator=(sass::vector&& other) + { + this->hash_ = 0; + this->elements_ = std::move(other); + return *this; + } + + // Some simple method delegations + void clear() { return elements_.clear(); } + size_t size() const { return elements_.size(); } + void reserve(size_t n) { return elements_.reserve(n); } + bool empty() const { return elements_.empty(); } + const T& at(size_t i) const { return elements_.at(i); } + const T& get(size_t i) const { return elements_[i]; } + const T& last() const { return elements_.back(); } + const T& first() const { return elements_.front(); } + + // Setter to ensure we reset hash value + void set(size_t i, const T& value) { + hash_ = 0; // reset hash + elements_.at(i) = value; + } + + // Setter to ensure we reset hash value + void setLast(const T& value) { + hash_ = 0; // reset hash + elements_.back() = value; + } + + // Check underlying containers for equality + bool operator== (const Vectorized& rhs) const + { + // Abort early if sizes do not match + if (size() != rhs.size()) return false; + // Abort early if hashes exist and don't match + if (hash_ && rhs.hash_ && hash_ != rhs.hash_) return false; + // Otherwise test each node for object equality in order + return std::equal(begin(), end(), rhs.begin(), ObjEqualityFn); + } + + // Derive unequal operator from equality check + bool operator!= (const Vectorized& rhs) const + { + return !(*this == rhs); + } + + // Implicitly get the sass::vector from our object + // Makes the Vector directly assignable to sass::vector + // You are responsible to make a copy if needed + // Note: since this returns the real object, we can't + // Note: guarantee that the hash will not get out of sync + // operator sass::vector& () { hash_ = 0; return elements_; } + // operator const sass::vector& () const { return elements_; } + + // Explicitly request all elements as a real sass::vector + // You are responsible to make a copy if needed + // Note: since this returns the real object, we can't + // Note: guarantee that the hash will not get out of sync + sass::vector& elements() { hash_ = 0; return elements_; } + const sass::vector& elements() const { return elements_; } + + // Insert all items from compatible vector + void concat(const sass::vector& v) + { + if (v.empty()) return; + hash_ = 0; // reset hash + elements_.insert(end(), + v.begin(), v.end()); + } + + // Insert all items from compatible vector + void concat(sass::vector&& v) + { + if (v.empty()) return; + hash_ = 0; // reset hash + elements_.insert(elements_.end(), + std::make_move_iterator(v.begin()), + std::make_move_iterator(v.end())); + } + + // Syntactic sugar for pointers + void concat(const Vectorized* v) + { + if (v != nullptr) { + return concat(v->elements_); + } + } + + // Insert one item on the front + void unshift(const T& element) + { + hash_ = 0; // reset hash + elements_.insert(begin(), + std::copy(element)); + } + + // Insert one item on the front + void unshift(T&& element) + { + hash_ = 0; // reset hash + elements_.insert(begin(), + std::move(element)); + } + + // Remove and return item on the front + T shift() { + T head = first(); + hash_ = 0; // reset hash + elements_.erase(begin()); + return head; + } + + // Remove and return item on the back + T pop() { + T tail = last(); + hash_ = 0; // reset hash + elements_.pop_back(); + return tail; + } + + // Insert one item on the back + // ToDo: rename this to push? + void append(const T& element) + { + hash_ = 0; // reset hash + elements_.emplace_back(element); + } + + // Insert one item on the back + // ToDo: rename this to push? + void append(T&& element) + { + hash_ = 0; // reset hash + elements_.emplace_back(std::move(element)); + } + + // Check if an item already exists + // Uses underlying object `operator==` + // E.g. compares the actual objects + bool contains(const T& el) const + { + for (const T& rhs : elements_) { + // Test the underlying objects for equality + // A std::find checks for pointer equality + if (ObjEqualityFn(el, rhs)) { + return true; + } + } + return false; + } + + // Check if an item already exists + // Uses underlying object `operator==` + // E.g. compares the actual objects + bool contains(const V* el) const + { + for (const T& rhs : elements_) { + // Test the underlying objects for equality + // A std::find checks for pointer equality + if (PtrObjEqualityFn(el, rhs.ptr())) { + return true; + } + } + return false; + } + + template + typename sass::vector::iterator insert(P position, const T& val) { + return elements_.insert(position, val); + } + + template + typename sass::vector::iterator insert(P position, T&& val) { + return elements_.insert(position, std::move(val)); + } + + template + void eraseIf(UnaryPredicate* predicate) + { + elements_.erase( + std::remove_if( + elements_.begin(), + elements_.end(), + predicate), + elements_.end()); + } + + size_t hash() const + { + if (hash_ == 0) { + hash_start(hash_, typeid(Vectorized).hash_code()); + for (auto child : elements_) { + hash_combine(hash_, child->hash()); + } + } + return hash_; + } + + typename sass::vector::iterator end() { return elements_.end(); } + typename sass::vector::iterator begin() { return elements_.begin(); } + typename sass::vector::const_iterator end() const { return elements_.end(); } + typename sass::vector::const_iterator begin() const { return elements_.begin(); } + typename sass::vector::iterator erase(typename sass::vector::iterator el) { return elements_.erase(el); } + typename sass::vector::const_iterator erase(typename sass::vector::const_iterator el) { return elements_.erase(el); } + + }; + + ///////////////////////////////////////////////////////////////////////// + // Mixin class for AST nodes that should behave like a hash table. Uses an + // extra internally to maintain insertion order for iteration. + ///////////////////////////////////////////////////////////////////////// + + template + class Hashed { + + public: + + using ordered_map_type = typename OrderedMap< + K, T, ObjHash, ObjEquality, + Sass::Allocator>, + sass::vector> + >; + + protected: + + // The main underlying container + ordered_map_type elements_; + + // Hash is only calculated once and afterwards the value + // must not be mutated, which is the case with how sass + // works, although we must be a bit careful not to alter + // any value that has already been added to a set or map. + // Must create a copy if you need to alter such an object. + mutable size_t hash_; + + public: + + Hashed() : + elements_(), + hash_(0) + {} + + // Copy constructor + Hashed(const Hashed& copy) : + elements_(), hash_(0) + { + elements_ = copy.elements_; + }; + + // Move constructor + Hashed(Hashed&& move) noexcept : + elements_(std::move(move.elements_)), + hash_(move.hash_) {}; + + // Move constructor + Hashed(ordered_map_type&& values) : + elements_(std::move(values)), + hash_(0) {}; + + size_t size() const { return elements_.size(); } + bool empty() const { return elements_.empty(); } + + bool has(const K& k) const { + return elements_.count(k) == 1; + } + + T at(const K& k) const { + auto it = elements_.find(k); + if (it == elements_.end()) return {}; + else return it->second; + } + + bool erase(const K& key) + { + // Would be faster by quite a bit (2% for bolt) + // return elements_.unordered_erase(key) != 0; + return elements_.erase(key) != 0; + } + + typename ordered_map_type::const_iterator find(const K& key) const + { + return elements_.find(key); + } + + typename ordered_map_type::iterator find(const K& key) + { + return elements_.find(key); + } + + void insert(std::pair&& kv) + { + elements_.insert(kv); + } + + void insert(const std::pair& kv) + { + elements_.insert(kv); + } + + void insert(const K& key, const T& val) + { + insert(std::make_pair(key, val)); + } + + void insertOrSet(std::pair& kv) + { + auto exists = elements_.find(kv.first); + if (exists == elements_.end()) { + // Insert a new entry + elements_.insert(kv); + } + else { + // Update existing entry + exists.value() = kv.second; + } + } + + void insertOrSet(const K& key, const T& val) + { + elements_[key] = val; + } + + void insertOrSet(const K& key, T&& val) + { + elements_[key] = std::move(val); + } + + // Return modifiable reference + ordered_map_type& elements() { + return elements_; + } + + sass::vector keys() const { + sass::vector list; + for (auto kv : elements_) { + list.emplace_back(kv.first); + } + return list; + } + sass::vector values() const { + sass::vector list; + for (auto kv : elements_) { + list.emplace_back(kv.second); + } + return list; + } + + size_t hash() const + { + if (hash_ == 0) { + hash_start(hash_, typeid(this).hash_code()); + for (auto kv : elements_) { + hash_combine(hash_, kv.first->hash()); + hash_combine(hash_, kv.second->hash()); + } + } + return hash_; + } + + typename ordered_map_type::iterator end() { return elements_.end(); } + typename ordered_map_type::iterator begin() { return elements_.begin(); } + typename ordered_map_type::const_iterator end() const { return elements_.end(); } + typename ordered_map_type::const_iterator begin() const { return elements_.begin(); } + + }; + +}; + +#endif diff --git a/src/ast_css.cpp b/src/ast_css.cpp new file mode 100644 index 0000000000..cec65c232d --- /dev/null +++ b/src/ast_css.cpp @@ -0,0 +1,517 @@ +#include "ast_css.hpp" + +#include "ast_selectors.hpp" + +#include "css_invisible.hpp" + +namespace Sass { + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + CssNode::CssNode( + const SourceSpan& pstate) : + AstNode(pstate) + {} + + CssNode::CssNode(const CssNode* ptr) : + AstNode(ptr) + {} + + bool CssNode::isInvisible() const + { + IsCssInvisibleVisitor visitor(true, false); + return const_cast(this)->accept(&visitor); + } + + bool CssNode::isInvisibleCss() const + { + IsCssInvisibleVisitor visitor(true, false); + return const_cast(this)->accept(&visitor); + } + + bool CssNode::isInvisibleHidingComments() const + { + IsCssInvisibleVisitor visitor(true, true); + return const_cast(this)->accept(&visitor); + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + CssParentNode::CssParentNode( + const SourceSpan& pstate, + CssParentNode* parent, + CssNodeVector&& children) : + CssNode(pstate), + Vectorized(std::move(children)), + parent_(parent) + {} + + CssParentNode::CssParentNode( + const CssParentNode* ptr, + bool childless) : + CssNode(ptr), + Vectorized(ptr, childless), + parent_(ptr->parent_) + {} + + // Adds [node] as a child of the given [parent]. The parent + // is copied unless it's the latter most child of its parent. + void CssParentNode::addChildAt(CssParentNode* child, bool outOfOrder) + { + // Check if we have a valid parent + if (outOfOrder && parent() != nullptr) { + // Check that parent is visible css + // if (!parent()->isInvisibleCss()) { + // Skip if we are last item in parent + if (parent()->last() != this) { + // Get iterator to following siblings + auto it = parent()->begin(); + // Search ourself inside parent + while (it != parent()->end()) { + if (it->ptr() == this) break; + it += 1; + } + // Search for first visible sibling + while (++it != parent()->end()) { + // Special context for invisibility! + // dart calls this out to the parent + const CssNode* sibling = *it; + if (!sibling->isInvisibleCss()) { + // Retain and append copy of parent + auto copy = SASS_MEMORY_RESECT(this); + parent()->addChildAt(copy, false); + copy->elements_.push_back(child); + child->parent(copy); + return; + } + } + } + // } + } + // Add child to parent + child->parent(this); + append(child); + } + // EO addChildAt + + bool CssParentNode::isInvisibleCss() const + { + for (auto child : elements()) { + if (!child->isInvisibleCss()) { + return false; + } + } + return true; + } + + // CssNode* CssParentNode::produce() const { + // CssNodeVector copy; + // for (CssNode* child : elements_) { + // copy.emplace_back(child->produce()); + // } + // return SASS_MEMORY_NEW(CssParentNode, + // pstate_, parent_, std::move(copy)); + // } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + CssRoot::CssRoot( + const SourceSpan& pstate, + CssNodeVector&& children) : + CssParentNode( + pstate, nullptr, + std::move(children)) + {} + + CssRoot::CssRoot( + const CssRoot* ptr, + bool childless) : + CssParentNode( + ptr, childless) + {} + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + CssString::CssString( + const SourceSpan& pstate, + const sass::string& text) : + AstNode(pstate), + text_(text) + {} + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + CssStringList::CssStringList( + const SourceSpan& pstate, + StringVector&& texts) : + AstNode(pstate), + texts_(std::move(texts)) + {} + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + CssAtRule::CssAtRule( + const SourceSpan& pstate, + CssParentNode* parent, + CssString* name, + CssString* value, + bool isChildless, + CssNodeVector&& children) : + CssParentNode( + pstate, parent, + std::move(children)), + name_(name), + value_(value), + isChildless_(isChildless) + {} + + CssAtRule::CssAtRule( + const CssAtRule* ptr, + bool childless) : + CssParentNode( + ptr, childless), + name_(ptr->name_), + value_(ptr->value_), + isChildless_(ptr->isChildless_) + {} + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + CssComment::CssComment( + const SourceSpan& pstate, + const sass::string& text, + bool preserve) : + CssNode(pstate), + text_(text), + isPreserved_(preserve) + {} + + CssComment::CssComment( + const CssComment* ptr) : + CssNode(ptr), + text_(ptr->text_), + isPreserved_(ptr->isPreserved_) + {} + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + CssDeclaration::CssDeclaration( + const SourceSpan& pstate, + CssString* name, + Value* value, + bool is_custom_property) : + CssNode(pstate), + name_(name), + value_(value), + is_custom_property_(is_custom_property) + {} + + CssDeclaration::CssDeclaration( + const CssDeclaration* ptr) : + CssNode(ptr), + name_(ptr->name_), + value_(ptr->value_), + is_custom_property_(ptr->is_custom_property_) + {} + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // Value constructor + CssImport::CssImport( + const SourceSpan& pstate, + CssString* url, + CssString* modifiers) : + CssNode(pstate), + url_(url), + modifiers_(modifiers), + outOfOrder_(false) + {} + + // Copy constructor + CssImport::CssImport( + const CssImport* ptr) : + CssNode(ptr), + url_(ptr->url_), + modifiers_(ptr->modifiers_), + outOfOrder_(ptr->outOfOrder_) + {} + + ///////////////////////////////////////////////////////////////////////// + // A block within a `@keyframes` rule. + // For example, `10% {opacity: 0.5}`. + ///////////////////////////////////////////////////////////////////////// + + // Value constructor + CssKeyframeBlock::CssKeyframeBlock( + const SourceSpan& pstate, + CssParentNode* parent, + CssStringList* selector, + CssNodeVector&& children) : + CssParentNode( + pstate, parent, + std::move(children)), + selector_(selector) + {} + + // Copy constructor + CssKeyframeBlock::CssKeyframeBlock( + const CssKeyframeBlock* ptr, + bool childless) : + CssParentNode( + ptr, childless), + selector_(ptr->selector_) + {} + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + CssStyleRule::CssStyleRule( + const SourceSpan& pstate, + CssParentNode* parent, + SelectorList* selector, + CssNodeVector&& children) : + CssParentNode( + pstate, parent, + std::move(children)), + selector_(selector), + original98_(selector ? selector->produce() : nullptr) + {} + + CssStyleRule::CssStyleRule( + const CssStyleRule* ptr, + bool childless) : + CssParentNode( + ptr, childless), + selector_(ptr->selector_), + original98_(ptr->original98_) + {} + + ///////////////////////////////////////////////////////////////////////// + + bool CssStyleRule::isInvisibleCss() const + { + if (const SelectorList* sl = selector()) { + if (sl->isInvisible()) return true; + } + for (const CssNode* item : elements()) { + if (!item->isInvisibleCss()) { + return false; + } + } + return true; + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + CssSupportsRule::CssSupportsRule( + const SourceSpan& pstate, + CssParentNode* parent, + ValueObj condition, + CssNodeVector&& children) : + CssParentNode( + pstate, parent, + std::move(children)), + condition_(condition) + {} + + CssSupportsRule::CssSupportsRule( + const CssSupportsRule* ptr, + bool childless) : + CssParentNode( + ptr, childless), + condition_(ptr->condition_) + {} + + ///////////////////////////////////////////////////////////////////////// + // A plain CSS `@media` rule after it has been evaluated. + ///////////////////////////////////////////////////////////////////////// + + // Value constructor + CssMediaRule::CssMediaRule( + const SourceSpan& pstate, + CssParentNode* parent, + const CssMediaQueryVector& queries, + CssNodeVector&& children) : + CssParentNode( + pstate, parent, + std::move(children)), + queries_(queries) + {} + + // Copy constructor + CssMediaRule::CssMediaRule( + const CssMediaRule* ptr, + bool childless) : + CssParentNode( + ptr, childless), + queries_(ptr->queries_) + {} + + // Used by Extension::assertCompatibleMediaContext + bool CssMediaRule::operator== (const CssMediaRule& rhs) const { + return queries_ == rhs.queries_; + } + // EO operator== + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + CssMediaQuery::CssMediaQuery( + const SourceSpan& pstate, + const sass::string& type, + const sass::string& modifier, + const StringVector& features) : + AstNode(pstate), + type_(type), + modifier_(modifier), + conjunction_(true), + features_(features) + {} + + CssMediaQuery::CssMediaQuery( + const SourceSpan& pstate, + sass::string&& type, + sass::string&& modifier, + StringVector&& features) : + AstNode(pstate), + type_(std::move(type)), + modifier_(std::move(modifier)), + conjunction_(true), + features_(std::move(features)) + {} + + CssMediaQuery::CssMediaQuery( + const SourceSpan& pstate, + StringVector && conditions, + bool conjunction) : + AstNode(pstate), + conjunction_(conjunction), + features_(std::move(conditions)) + {} + + // Used by Extension::assertCompatibleMediaContext + bool CssMediaQuery::operator==(const CssMediaQuery& rhs) const + { + return type_ == rhs.type_ + && modifier_ == rhs.modifier_ + && features_ == rhs.features_; + } + // EO operator== + + // Implemented after dart-sass (maybe move to other class?) + CssMediaQuery* CssMediaQuery::merge(CssMediaQuery* other) + { + + // Import namespace locally + using namespace StringUtils; + + // Get a few references from both objects + const sass::string& thisType(this->type()); + const sass::string& otherType(other->type()); + const sass::string& thisModifier(this->modifier()); + const sass::string& otherModifier(other->modifier()); + const StringVector& thisFeatures(this->features()); + const StringVector& otherFeatures(other->features()); + + // Check for the most simplistic case first + if (thisType.empty() && otherType.empty()) { + StringVector features; + features.reserve(thisFeatures.size() + otherFeatures.size()); + features.insert(features.end(), thisFeatures.begin(), thisFeatures.end()); + features.insert(features.end(), otherFeatures.begin(), otherFeatures.end()); + return SASS_MEMORY_NEW(CssMediaQuery, pstate(), + "", "", std::move(features)); + } + + // bool typesAreEqual(equalsIgnoreCase(thisType, otherType)); + bool thisMatchesAll(this->matchesAllTypes()); + bool otherMatchesAll(other->matchesAllTypes()); + bool thisModifierIsNot(equalsIgnoreCase(thisModifier, "not", 3)); + bool otherModifierIsNot(equalsIgnoreCase(otherModifier, "not", 3)); + + // The queries have different "not" modifier + if (thisModifierIsNot != otherModifierIsNot) { + // If types are equal, we can merge some cases + if (equalsIgnoreCase(thisType, otherType)) { + // Sort arrays into shorter and bigger one + const StringVector& negativeFeatures = thisModifierIsNot ? thisFeatures : otherFeatures; + const StringVector& positiveFeatures = thisModifierIsNot ? otherFeatures : thisFeatures; + // If the negative features are a subset of the positive features, the + // query is empty. For example, `not screen and (color)` has no + // intersection with `screen and (color) and (grid)`. + // However, `not screen and (color)` *does* intersect with `screen and + // (grid)`, because it means `not (screen and (color))` and so it allows + // a screen with no color but with a grid. + if (listIsSubsetOrEqual(negativeFeatures, positiveFeatures)) { + return SASS_MEMORY_NEW(CssMediaQuery, pstate(), ""); + } + // Otherwise we can't merge them + return nullptr; + } + // We established that types differ + // Check if one matches everything + if (thisMatchesAll) return nullptr; + if (otherMatchesAll) return nullptr; + // Check which modifier was "not" + return thisModifierIsNot ? other : this; + } + // Our modifier equals "not" + else if (thisModifierIsNot) { + // CSS has no way of representing "neither screen nor print". + if (!equalsIgnoreCase(thisType, otherType)) return nullptr; + // Sort arrays into shorter and bigger one + bool thisIsBigger(thisFeatures.size() > otherFeatures.size()); + const StringVector& moreFeatures = thisIsBigger ? thisFeatures : otherFeatures; + const StringVector& fewerFeatures = thisIsBigger ? otherFeatures : thisFeatures; + // If one set of features is a superset of the other, + // use those features because they're strictly narrower. + if (listIsSubsetOrEqual(fewerFeatures, moreFeatures)) { + // Ignore the lesser features (included in other) + return SASS_MEMORY_NEW(CssMediaQuery, pstate(), + thisType, thisModifier, moreFeatures); + } + // Otherwise, there's no way to + // represent the intersection. + return nullptr; + } + + // Check if types are not the same + if (!equalsIgnoreCase(thisType, otherType)) { + // Check that nothing has an "all" modifier + if (!thisMatchesAll && !otherMatchesAll) { + return SASS_MEMORY_NEW(CssMediaQuery, pstate(), ""); + } + } + + // Concatenate both features + StringVector features; + features.reserve(thisFeatures.size() + otherFeatures.size()); + features.insert(features.end(), thisFeatures.begin(), thisFeatures.end()); + features.insert(features.end(), otherFeatures.begin(), otherFeatures.end()); + + // Check if we should return other query + if (this->matchesAllTypes() && !(otherMatchesAll && thisType.empty())) { + return SASS_MEMORY_NEW(CssMediaQuery, pstate(), + otherType, otherModifier, std::move(features)); + } + + // Return same query with concatenated features + return SASS_MEMORY_NEW(CssMediaQuery, pstate(), + thisType, thisModifier, std::move(features)); + } + // EO merge + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + +} diff --git a/src/ast_css.hpp b/src/ast_css.hpp new file mode 100644 index 0000000000..a3bd28e07e --- /dev/null +++ b/src/ast_css.hpp @@ -0,0 +1,636 @@ +#ifndef SASS_AST_CSS_HPP +#define SASS_AST_CSS_HPP + +// sass.hpp must go before all system headers +// to get the __EXTENSIONS__ fix on Solaris. +#include "capi_sass.hpp" + +#include "ast_nodes.hpp" +#include "ast_selectors.hpp" +#include "visitor_css.hpp" +#include "ast_statements.hpp" +#include "environment_stack.hpp" +// #include "ast_def_macros.hpp" + +namespace Sass { + + ///////////////////////////////////////////////////////////////////////// + // Base class for all css related AST nodes. + ///////////////////////////////////////////////////////////////////////// + + class CssNode : public AstNode, + public CssVisitable, + public CssVisitable + { + + private: + + // Whether this was generated from the last node in a + // nested Sass tree that got flattened during evaluation. + // ADD_CONSTREF(bool, isGroupEnd); + + public: + + // Value constructor + CssNode(const SourceSpan& pstate); + + // Copy constructor + CssNode(const CssNode* ptr); + + // Needed here to avoid ambiguity from base-classes!?? + virtual void accept(CssVisitor* visitor) override = 0; + virtual bool accept(CssVisitor* visitor) override = 0; + + virtual bool isInvisible() const; + + // Return if node should be printed (to be specialized). + virtual bool isInvisibleCss() const; + + virtual bool isInvisibleHidingComments() const; + + // Returns the at-rule name for [node], or `null` if it's not an at-rule. + virtual const sass::string& getAtRuleName() const { return Strings::empty; } + + virtual CssNode* produce() { return this; } + + // Is this really obsolete now? + // size_t tabs() const { return 0; } + // void tabs(size_t tabs) const { } + + // Declare up-casting methods + DECLARE_ISA_CASTER(CssAtRule); + DECLARE_ISA_CASTER(CssMediaRule); + DECLARE_ISA_CASTER(CssStyleRule); + DECLARE_ISA_CASTER(CssSupportsRule); + }; + // EO CssNode + + ///////////////////////////////////////////////////////////////////////// + // Base class for css nodes that can have children and a parent. + ///////////////////////////////////////////////////////////////////////// + + class CssParentNode : public CssNode, + public Vectorized + { + private: + + // This must be a pointer to avoid circular references + // Means it has a possibility of being a dangling pointer + ADD_PROPERTY(CssParentNode*, parent); + + public: + + // Value constructor + CssParentNode( + const SourceSpan& pstate, + CssParentNode* parent, + CssNodeVector&& children = {}); + + // Copy constructor + CssParentNode( + const CssParentNode* ptr, + bool childless = false); + + // Adds [node] as a child of the given [parent]. The parent + // is copied unless it's the latter most child of its parent. + void addChildAt(CssParentNode* node, bool outOfOrder = false); + + // Return false if a single item is visible + bool isInvisibleCss() const override; + + // Must be implemented in derived classes + virtual CssParentNode* copy(SASS_MEMORY_ARGS bool childless) const = 0; + + // Returns if items should bubble further up (to be specialized) + virtual bool bubbles(bool stopAtMediaRule = false) const { return false; } + + // Helper function to bubble through parents + CssParentNode* bubbleThrough(bool stopAtMediaRule = false) + { + return parent_ && bubbles(stopAtMediaRule) ? + parent_->bubbleThrough(stopAtMediaRule) : this; + } + + // virtual CssNode* produce() const override; + + + // Declare up-casting methods + DECLARE_ISA_CASTER(CssAtRule); + }; + // EO CssParentNode + + ///////////////////////////////////////////////////////////////////////// + // A plain CSS string + ///////////////////////////////////////////////////////////////////////// + + class CssString final : public AstNode + { + private: + + ADD_CONSTREF(sass::string, text); + + public: + + // Value constructor + CssString( + const SourceSpan& pstate, + const sass::string& text); + + bool empty() const { return text_.empty(); } + + }; + // EO CssString + + ///////////////////////////////////////////////////////////////////////// + // A plain list of CSS strings + ///////////////////////////////////////////////////////////////////////// + + class CssStringList final : public AstNode + { + private: + + ADD_CONSTREF(StringVector, texts); + + public: + + // Value constructor + CssStringList( + const SourceSpan& pstate, + StringVector&& texts); + + }; + // EO CssStringList + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + class CssComment final : public CssNode + { + private: + ADD_CONSTREF(sass::string, text); + ADD_CONSTREF(bool, isPreserved); + public: + CssComment(const SourceSpan& pstate, + const sass::string& text, + bool preserve = false); + CssComment(const CssComment* ptr); + + // Css visitor and rendering entry function + void accept(CssVisitor* visitor) override final { + return visitor->visitCssComment(this); + } + bool accept(CssVisitor* visitor) override final { + return visitor->visitCssComment(this); + } + }; + // EO CssComment + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + class CssDeclaration final : public CssNode + { + private: + // The name of this declaration. + ADD_CONSTREF(CssStringObj, name); + // The value of this declaration. + ADD_CONSTREF(ValueObj, value); + ADD_CONSTREF(bool, is_custom_property); + public: + CssDeclaration(const SourceSpan& pstate, + CssString* name, Value* value, + bool is_custom_property = false); + CssDeclaration(const CssDeclaration* ptr); + + // Css visitor and rendering entry function + void accept(CssVisitor* visitor) override final { + return visitor->visitCssDeclaration(this); + } + bool accept(CssVisitor* visitor) override final { + return visitor->visitCssDeclaration(this); + } + }; + // EO CssDeclaration + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // A css import is static in nature and + // can only have one single import url. + class CssImport final : public CssNode + { + private: + + // The url including quotes. + ADD_CONSTREF(CssStringObj, url); + + // The supports condition attached to this import. + ADD_CONSTREF(CssStringObj, modifiers); + + // The media query attached to this import. + // ADD_CONSTREF(CssMediaQueryVector, media); + + // Flag to hoist import to the top. + // This case is possible if an `@import` within + // an imported css file is inside a `CssStyleRule`. + ADD_CONSTREF(bool, outOfOrder); + + public: + + // Standard value constructor + CssImport( + const SourceSpan& pstate, + CssString* url = nullptr, + CssString* modifiers = nullptr); + + // Copy constructor + CssImport(const CssImport* ptr); + + // Css visitor and rendering entry function + void accept(CssVisitor* visitor) override final { + return visitor->visitCssImport(this); + } + bool accept(CssVisitor* visitor) override final { + return visitor->visitCssImport(this); + } + + }; + // EO CssImport + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + class CssRoot final : public CssParentNode + { + public: + + // Value constructor + CssRoot( + const SourceSpan& pstate, + CssNodeVector&& children = {}); + + // Copy constructor + CssRoot( + const CssRoot* ptr, + bool childless = false); + + // Css visitor and rendering entry function + void accept(CssVisitor* visitor) override final { + return visitor->visitCssRoot(this); + } + bool accept(CssVisitor* visitor) override final { + return visitor->visitCssRoot(this); + } + + CssNode* produce() override final { + CssNodeVector copy; + for (CssNode* child : elements_) { + copy.emplace_back(child->produce()); + } + return SASS_MEMORY_NEW(CssRoot, + pstate_, std::move(copy)); + } + + CssRoot* copy(SASS_MEMORY_ARGS bool childless) const override final { + return SASS_MEMORY_NEW_DBG(CssRoot, this); + } + + }; + // EO CssRoot + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + class CssAtRule final : public CssParentNode + { + private: + + ADD_CONSTREF(CssStringObj, name); + + ADD_CONSTREF(CssStringObj, value); + + // Whether the rule has no children and should be emitted + // without curly braces. This implies `children.isEmpty`, + // but the reverse is not true - for a rule like `@foo {}`, + // [children] is empty but [isChildless] is `false`. + // It means we didn't see any `{` when parsed. + ADD_CONSTREF(bool, isChildless); + + public: + + // Value constructor + CssAtRule( + const SourceSpan& pstate, + CssParentNode* parent, + CssString* name, + CssString* value, + bool isChildless = false, + CssNodeVector&& children = {}); + + // Copy constructor + CssAtRule( + const CssAtRule* ptr, + bool childless = false); + + bool isInvisibleCss() const override final { + return false; + } + + // Returns the at-rule name for [node], or `null` if it's not an at-rule. + const sass::string& getAtRuleName() const override final { + return name()->text(); + } + + // Css visitor and rendering entry function + void accept(CssVisitor* visitor) override final { + return visitor->visitCssAtRule(this); + } + bool accept(CssVisitor* visitor) override final { + return visitor->visitCssAtRule(this); + } + + CssAtRule* copy(SASS_MEMORY_ARGS bool childless) const override final { + return SASS_MEMORY_NEW_DBG(CssAtRule, this); + } + + // Define isaCssAtRule up-cast function + IMPLEMENT_ISA_CASTER(CssAtRule); + }; + // EO CssAtRule + + ///////////////////////////////////////////////////////////////////////// + // A block within a `@keyframes` rule. + // For example, `10% {opacity: 0.5}`. + ///////////////////////////////////////////////////////////////////////// + class CssKeyframeBlock final : public CssParentNode + { + private: + + // The selector for this block. + ADD_CONSTREF(CssStringListObj, selector); + + public: + + // Value constructor + CssKeyframeBlock( + const SourceSpan& pstate, + CssParentNode* parent, + CssStringList* selector, + CssNodeVector&& children = {}); + + // Copy constructor + CssKeyframeBlock( + const CssKeyframeBlock* ptr, + bool childless = false); + + // Return a copy with empty children + // CssKeyframeBlock* copyWithoutChildren(); + + // Css visitor and rendering entry function + void accept(CssVisitor* visitor) override final { + return visitor->visitCssKeyframeBlock(this); + } + bool accept(CssVisitor* visitor) override final { + return visitor->visitCssKeyframeBlock(this); + } + + CssKeyframeBlock* copy(SASS_MEMORY_ARGS bool childless) const override final { + return SASS_MEMORY_NEW_DBG(CssKeyframeBlock, this); + } + + }; + // EO CssKeyframeBlock + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + class CssStyleRule final : public CssParentNode + { + private: + + ADD_CONSTREF(SelectorListObj, selector); + ADD_CONSTREF(SelectorListObj, original98); + + public: + + // Value constructor + CssStyleRule( + const SourceSpan& pstate, + CssParentNode* parent, + SelectorList* selector, + CssNodeVector&& children = {}); + + // Copy constructor + CssStyleRule( + const CssStyleRule* ptr, + bool childless = false); + + // Selector and one child must be visible + bool isInvisibleCss() const override final; + + // Media rules are sometimes transparent, sometimes not + bool bubbles(bool stopAtMediaRule) const override final { return true; } + + // Css visitor and rendering entry function + void accept(CssVisitor* visitor) override final { + return visitor->visitCssStyleRule(this); + } + bool accept(CssVisitor* visitor) override final { + return visitor->visitCssStyleRule(this); + } + + CssStyleRule* produce() override final { + CssNodeVector copy; + for (CssNode* child : elements_) { + copy.emplace_back(child->produce()); + } + return SASS_MEMORY_NEW(CssStyleRule, + pstate_, parent_, + original98_->produce(), + std::move(copy)); + } + + // Declare via macro to allow line/col debugging + CssStyleRule* copy(SASS_MEMORY_ARGS bool childless) const override final { + return SASS_MEMORY_NEW_DBG(CssStyleRule, this, childless); + } + + // Define isaCssStyleRule up-cast function + IMPLEMENT_ISA_CASTER(CssStyleRule); + }; + // EO CssStyleRule + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + class CssSupportsRule final : public CssParentNode + { + private: + + ADD_CONSTREF(ValueObj, condition); + + public: + + // Value constructor + CssSupportsRule( + const SourceSpan& pstate, + CssParentNode* parent, + ValueObj condition, + CssNodeVector&& children = {}); + + // Copy constructor + CssSupportsRule( + const CssSupportsRule* ptr, + bool childless = false); + + // Returns the at-rule name for [node], or `null` if it's not an at-rule. + const sass::string& getAtRuleName() const override final { return Strings::supports; } + + // Css visitor and rendering entry function + void accept(CssVisitor* visitor) override final { + return visitor->visitCssSupportsRule(this); + } + bool accept(CssVisitor* visitor) override final { + return visitor->visitCssSupportsRule(this); + } + + // Declare via macro to allow line/col debugging + CssSupportsRule* copy(SASS_MEMORY_ARGS bool childless) const override final { + return SASS_MEMORY_NEW_DBG(CssSupportsRule, this, childless); + } + + // Define isaCssSupportsRule up-cast function + IMPLEMENT_ISA_CASTER(CssSupportsRule); + }; + // EO CssSupportsRule + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // Media Queries after they have been evaluated + // Representing the static or resulting css + class CssMediaQuery final : public AstNode { + + // The media type, for example "screen" or "print". + // This may be `null`. If so, [features] will not be empty. + ADD_CONSTREF(sass::string, type); + + // The modifier, probably either "not" or "only". + // This may be `null` if no modifier is in use. + ADD_CONSTREF(sass::string, modifier); + + ADD_CONSTREF(bool, conjunction); + + // Feature queries, including parentheses. + ADD_CONSTREF(StringVector, features); + + public: + + // Value copy constructor + CssMediaQuery( + const SourceSpan& pstate, + const sass::string& type, + const sass::string& modifier, + const StringVector& features); + + // Value move constructor + CssMediaQuery( + const SourceSpan& pstate, + sass::string&& type, + sass::string&& modifier = "", + StringVector&& features = {}); + + // Value move constructor + CssMediaQuery( + const SourceSpan& pstate, + StringVector&& conditions, + bool conjunction = true); + + // Returns true if this query is empty + // Meaning it has no type and features + bool empty() const { + return type_.empty() + && modifier_.empty() + && features_.empty(); + } + + // Whether this media query matches all media types. + bool matchesAllTypes() const { + return type_.empty() || StringUtils::equalsIgnoreCase(type_, "all", 3); + } + + // Check if two instances are considered equal + bool operator== (const CssMediaQuery& rhs) const; + + // Merges this with [other] and adds a query that matches the intersection + // of both inputs to [result]. Returns false if the result is unrepresentable + CssMediaQuery* merge(CssMediaQuery* other); + + }; + // EO CssMediaQuery + + ///////////////////////////////////////////////////////////////////////// + // A plain CSS `@media` rule after it has been evaluated. + ///////////////////////////////////////////////////////////////////////// + class CssMediaRule final : public CssParentNode + { + private: + + // The queries for this rule (this is never empty). + ADD_CONSTREF(Vectorized, queries); + + public: + + // Value constructor + CssMediaRule(const SourceSpan& pstate, + CssParentNode* parent, + const CssMediaQueryVector& queries, + CssNodeVector&& children = {}); + + // Copy constructor + CssMediaRule( + const CssMediaRule* ptr, + bool childless = false); + + // Check if we or any children are invisible + bool isInvisibleCss() const override final { + return queries_.empty() || + CssParentNode::isInvisibleCss(); + } + + // Media rules are sometimes transparent, sometimes not + bool bubbles(bool stopAtMediaRule) const override final { + return stopAtMediaRule == false; + } + + // Returns the at-rule name for [node], or `null` if it's not an at-rule. + const sass::string& getAtRuleName() const override final { return Strings::media; } + + // Css visitor and rendering entry function + void accept(CssVisitor* visitor) override final { + return visitor->visitCssMediaRule(this); + } + bool accept(CssVisitor* visitor) override final { + return visitor->visitCssMediaRule(this); + } + + // Check if two instances are considered equal + // Used by Extension::assertCompatibleMediaContext + bool operator== (const CssMediaRule& rhs) const; + + // Declare via macro to allow line/col debugging + CssMediaRule* copy(SASS_MEMORY_ARGS bool childless) const override final { + return SASS_MEMORY_NEW_DBG(CssMediaRule, this, childless); + } + + // Define isaCssMediaRule up-cast function + IMPLEMENT_ISA_CASTER(CssMediaRule); + }; + // EO CssMediaRule + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + +} + +#endif diff --git a/src/ast_def_macros.hpp b/src/ast_def_macros.hpp index 8a889cd9cc..ffca63d36d 100644 --- a/src/ast_def_macros.hpp +++ b/src/ast_def_macros.hpp @@ -1,140 +1,200 @@ #ifndef SASS_AST_DEF_MACROS_H #define SASS_AST_DEF_MACROS_H -// Helper class to switch a flag and revert once we go out of scope +#include "memory_allocator.hpp" + +// Helper class to switch a flag +// and revert once we go out of scope template -class LocalOption { +class LocalOption final { private: T* var; // pointer to original variable T orig; // copy of the original option public: - LocalOption(T& var) - { - this->var = &var; - this->orig = var; - } - LocalOption(T& var, T orig) - { - this->var = &var; - this->orig = var; - *(this->var) = orig; - } - void reset() + LocalOption(T& var, T current) : + var(&var), orig(var) { - *(this->var) = this->orig; + *(this->var) = current; } ~LocalOption() { *(this->var) = this->orig; } }; -#define LOCAL_FLAG(name,opt) LocalOption flag_##name(name, opt) -#define LOCAL_COUNT(name,opt) LocalOption cnt_##name(name, opt) +// Helper class to put something on a vector +// and revert once we go out of scope. +template +class LocalStack final { +private: + sass::vector& cnt; // container +public: + LocalStack(sass::vector& cnt, T push) : + cnt(cnt) + { + cnt.emplace_back(push); + } + ~LocalStack() { + cnt.pop_back(); + } +}; + +// Macros to help create and maintain local and recursive flag states +#define RAII_FLAG(name,opt) LocalOption flag_##name(name, opt) +#define RAII_PTR(var,name,opt) LocalOption flag_##name(name, opt) +#define RAII_SELECTOR(name,opt) LocalStack stack_##name(name, opt) +#define RAII_MODULE(name,opt) LocalStack stack_##name(name, opt) +// Macro to help impose maximum nesting to avoid stack overflow #define NESTING_GUARD(name) \ LocalOption cnt_##name(name, name + 1); \ - if (name > MAX_NESTING) throw Exception::NestingLimitError(pstate, traces); \ + if (name > SassMaxNesting) throw Exception::RecursionLimitError(); \ -#define ADD_PROPERTY(type, name)\ -protected:\ - type name##_;\ -public:\ - type name() const { return name##_; }\ - type name(type name##__) { return name##_ = name##__; }\ -private: - -#define HASH_PROPERTY(type, name)\ -protected:\ - type name##_;\ -public:\ - type name() const { return name##_; }\ - type name(type name##__) { hash_ = 0; return name##_ = name##__; }\ -private: +///////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////// -#define ADD_CONSTREF(type, name) \ +#define ADD_REF(type, name) \ protected: \ type name##_; \ public: \ + type& name() { return name##_; } \ const type& name() const { return name##_; } \ - void name(type name##__) { name##_ = name##__; } \ + void name(type&& name##__) { name##_ = std::move(name##__); } \ + void name(const type& name##__) { name##_ = name##__; } \ private: -#define HASH_CONSTREF(type, name) \ -protected: \ - type name##_; \ -public: \ +#define ADD_CONSTREF(type, name)\ +protected:\ + type name##_;\ +public:\ const type& name() const { return name##_; } \ - void name(type name##__) { hash_ = 0; name##_ = name##__; } \ + void name(type&& name##__) { name##_ = std::move(name##__); } \ + void name(const type& name##__) { name##_ = name##__; } \ private: -#ifdef DEBUG_SHARED_PTR - -#define ATTACH_ABSTRACT_AST_OPERATIONS(klass) \ - virtual klass* copy(sass::string, size_t) const = 0; \ - virtual klass* clone(sass::string, size_t) const = 0; \ - -#define ATTACH_VIRTUAL_AST_OPERATIONS(klass) \ - klass(const klass* ptr); \ - virtual klass* copy(sass::string, size_t) const override = 0; \ - virtual klass* clone(sass::string, size_t) const override = 0; \ +#define ADD_PROPERTY(type, name)\ +protected:\ + type name##_;\ +public:\ + type name() const { return name##_; }\ + void name(type name##__) { name##_ = name##__; }\ +private: -#define ATTACH_AST_OPERATIONS(klass) \ - klass(const klass* ptr); \ - virtual klass* copy(sass::string, size_t) const override; \ - virtual klass* clone(sass::string, size_t) const override; \ + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// -#else + #ifdef DEBUG_SHARED_PTR -#define ATTACH_ABSTRACT_AST_OPERATIONS(klass) \ - virtual klass* copy() const = 0; \ - virtual klass* clone() const = 0; \ + #define SASS_MEMORY_PARAMS file, line, + #define SASS_MEMORY_PARAMS_VOID file, line + #define SASS_MEMORY_POS __FILE__, __LINE__ + #define SASS_MEMORY_POS_VOID __FILE__, __LINE__ + #define SASS_MEMORY_ARGS sass::string file, size_t line, + #define SASS_MEMORY_ARGS_VOID sass::string file, size_t line -#define ATTACH_VIRTUAL_AST_OPERATIONS(klass) \ - klass(const klass* ptr); \ - virtual klass* copy() const override = 0; \ - virtual klass* clone() const override = 0; \ + #else -#define ATTACH_AST_OPERATIONS(klass) \ - klass(const klass* ptr); \ - virtual klass* copy() const override; \ - virtual klass* clone() const override; \ + #define SASS_MEMORY_PARAMS + #define SASS_MEMORY_PARAMS_VOID + #define SASS_MEMORY_POS + #define SASS_MEMORY_POS_VOID + #define SASS_MEMORY_ARGS + #define SASS_MEMORY_ARGS_VOID -#endif + #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); }; \ + #define DECLARE_ISA_CASTER(klass) \ + public: virtual klass* isa##klass() { return nullptr; } \ + public: virtual const klass* isa##klass() const { return nullptr; } \ -#ifdef DEBUG_SHARED_PTR + #define IMPLEMENT_ISA_CASTER(klass) \ + public: klass* isa##klass() final override { return this; } \ + public: const klass* isa##klass() const final override { return this; } \ - #define IMPLEMENT_AST_OPERATORS(klass) \ - klass* klass::copy(sass::string file, size_t line) const { \ - klass* cpy = SASS_MEMORY_NEW(klass, this); \ - cpy->trace(file, line); \ - return cpy; \ - } \ - klass* klass::clone(sass::string file, size_t line) const { \ - klass* cpy = copy(file, line); \ - cpy->cloneChildren(); \ - return cpy; \ + #define IMPLEMENT_ACCEPT(type, visitor, klass) \ + public: type accept(visitor##Visitor* visitor) override final { \ + return visitor->visit##klass(this); \ } \ -#else + #define IMPLEMENT_EQ_OPERATOR(subklass, klass) \ + public: bool operator==(const subklass& rhs) const override final { \ + auto sel = rhs.isa##klass(); \ + return sel ? *this == *sel : false; \ + } \ + public: bool operator==(const klass& rhs) const; \ - #define IMPLEMENT_AST_OPERATORS(klass) \ - klass* klass::copy() const { \ - return SASS_MEMORY_NEW(klass, this); \ + // Childless argument is passed to ctor + #define IMPLEMENT_SEL_COPY_CHILDREN(klass) \ + public: klass* copy(SASS_MEMORY_ARGS bool childless) const override final { \ + return SASS_MEMORY_NEW_DBG(klass, this, childless); \ } \ - klass* klass::clone() const { \ - klass* cpy = copy(); \ - cpy->cloneChildren(); \ - return cpy; \ + + // Childless argument is ignored on ctor + #define IMPLEMENT_SEL_COPY_IGNORE(klass) \ + public: klass* copy(SASS_MEMORY_ARGS bool childless) const override final { \ + return SASS_MEMORY_NEW_DBG(klass, this); \ } \ -#endif + ///////////////////////////////////////////////////////////////////////// + /* Wrap c++ pointers for C-API to anon-structs */ + ///////////////////////////////////////////////////////////////////////// + #define CAPI_WRAPPER(klass, strukt) \ + struct strukt* wrap() \ + { \ + /* This is a compile time cast and doesn't cost anything */ \ + return reinterpret_cast(this); \ + }; \ + /* Wrap the pointer for C-API */ \ + const struct strukt* wrap() const \ + { \ + /* This is a compile time cast and doesn't cost anything */ \ + return reinterpret_cast(this); \ + }; \ + /* Wrap the pointer for C-API */ \ + static struct strukt* wrap(klass* unwrapped) \ + { \ + /* Ensure we at least catch the most obvious stuff */ \ + if (unwrapped == nullptr) throw std::runtime_error( \ + "Null-Pointer passed to " #klass "::unwrap"); \ + /* Just delegate to wrap */ \ + return unwrapped->wrap(); \ + }; \ + /* Wrap the pointer for C-API */ \ + static const struct strukt* wrap(const klass* unwrapped) \ + { \ + /* Ensure we at least catch the most obvious stuff */ \ + if (unwrapped == nullptr) throw std::runtime_error( \ + "Null-Pointer passed to " #klass "::unwrap"); \ + /* Just delegate to wrap */ \ + return unwrapped->wrap(); \ + }; \ + /* Unwrap the pointer for C-API (potentially unsafe). */ \ + /* You must pass in a pointer you've got via wrap API. */ \ + /* Passing anything else will result in undefined behavior! */ \ + static klass& unwrap(struct strukt* wrapped) \ + { \ + /* Ensure we at least catch the most obvious stuff */ \ + if (wrapped == nullptr) throw std::runtime_error( \ + "Null-Pointer passed to " #klass "::unwrap"); \ + /* This is a compile time cast and doesn't cost anything */ \ + return *reinterpret_cast(wrapped); \ + }; \ + /* Unwrap the pointer for C-API (potentially unsafe). */ \ + /* You must pass in a pointer you've got via wrap API. */ \ + /* Passing anything else will result in undefined behavior! */ \ + static const klass& unwrap(const struct strukt* wrapped) \ + { \ + /* Ensure we at least catch the most obvious stuff */ \ + if (wrapped == nullptr) throw std::runtime_error( \ + "Null-Pointer passed to " #klass "::unwrap"); \ + /* This is a compile time cast and doesn't cost anything */ \ + return *reinterpret_cast(wrapped); \ + }; \ + + +///////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////// #endif diff --git a/src/ast_expressions.cpp b/src/ast_expressions.cpp new file mode 100644 index 0000000000..8cf3900777 --- /dev/null +++ b/src/ast_expressions.cpp @@ -0,0 +1,558 @@ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#include "ast_expressions.hpp" +#include "interpolation.hpp" + +#include + +namespace Sass { + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + SelectorExpression::SelectorExpression( + SourceSpan&& pstate) : + Expression(std::move(pstate)) + {} + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + ValueExpression::ValueExpression( + SourceSpan pstate, + ValueObj value) : + Expression(std::move(pstate)), + value_(value) + {} + + // Convert to string (only for debugging) + sass::string ValueExpression::toString() const + { + return value_->inspect(); + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + NullExpression::NullExpression( + SourceSpan pstate, + Null* value) : + Expression(std::move(pstate)), + value_(value) + {} + + // Convert to string (only for debugging) + sass::string NullExpression::toString() const + { + return "null"; + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + ColorExpression::ColorExpression( + SourceSpan pstate, + Color* value) : + Expression(std::move(pstate)), + value_(value) + {} + + // Convert to string (only for debugging) + sass::string ColorExpression::toString() const + { + return value_->inspect(); + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + NumberExpression::NumberExpression( + SourceSpan pstate, + Number* value) : + Expression(std::move(pstate)), + value_(value) + {} + + // Convert to string (only for debugging) + sass::string NumberExpression::toString() const + { + return value_->inspect(); + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + BooleanExpression::BooleanExpression( + SourceSpan pstate, + Boolean* value) : + Expression(std::move(pstate)), + value_(value) + {} + + // Convert to string (only for debugging) + sass::string BooleanExpression::toString() const + { + return value_ ? "true" : "false"; + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // Value constructor + StringExpression::StringExpression( + SourceSpan pstate, + InterpolationObj text, + bool hasQuotes) : + Expression(std::move(pstate)), + text_(text), + hasQuotes_(hasQuotes) + {} + + // Crutch instead of LiteralExpression + // Note: really not used very often + // ToDo: check for performance impact + StringExpression::StringExpression( + SourceSpan&& pstate, + sass::string&& text, + bool hasQuotes) : + Expression(std::move(pstate)), + text_(SASS_MEMORY_NEW(Interpolation, pstate, + SASS_MEMORY_NEW(String, pstate, std::move(text)))), + hasQuotes_(hasQuotes) + {} + + // find best quote_mark by detecting if the string contains any single + // or double quotes. When a single quote is found, we not we want a double + // quote as quote_mark. Otherwise we check if the string contains any double + // quotes, which will trigger the use of single quotes as best quote_mark. + uint8_t StringExpression::findBestQuote() const + { + using namespace Character; + bool containsDoubleQuote = false; + for (auto item : text_->elements()) { + if (auto str = item->isaString()) { // Ex + const auto& value = str->value(); + for (size_t i = 0; i < value.size(); i++) { + if (value[i] == $apos) return $quote; + if (value[i] == $quote) containsDoubleQuote = true; + } + } + else if (auto str = item->isaItplString()) { // Ex + const auto& value = str->text(); + for (size_t i = 0; i < value.size(); i++) { + if (value[i] == $apos) return $quote; + if (value[i] == $quote) containsDoubleQuote = true; + } + } + } + return containsDoubleQuote ? $apos : $quote; + } + + sass::string StringExpression::toString() const + { + InterpolationObj itpl = getAsInterpolation(); + return itpl->toString(); + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // Value constructor + SupportsExpression::SupportsExpression( + SourceSpan pstate, + SupportsCondition* condition) : + Expression(std::move(pstate)), + condition_(condition) + {} + + sass::string SupportsExpression::toString() const + { + // auto qwe = condition_; + sass::string msg = "("; + // return condition_->toString(); + msg += "NO INSPECT FOR SUPPORTS EXPRESSION"; + return msg + ")"; + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + ItplFnExpression::ItplFnExpression( + SourceSpan pstate, + Interpolation* itpl, + CallableArguments* arguments, + const sass::string& ns) : + InvocationExpression( + std::move(pstate), arguments), + itpl_(itpl), + ns_(ns) + {} + + // Interpolation that, when evaluated, produces the syntax of this string. + // Unlike [text], his doesn't resolve escapes and does include quotes for + // quoted strings. If [static] is true, this escapes any `#{` sequences in + // the string. If [quote] is passed, it uses that character as the quote mark; + // otherwise, it determines the best quote to add by looking at the string. + // Note: [static] is not yet implemented in LibSass (find where we need it) + InterpolationObj StringExpression::getAsInterpolation(bool escape, uint8_t quote) const + { + + using namespace Character; + + if (!hasQuotes()) return text_; + + if (!quote && hasQuotes()) quote = findBestQuote(); + + InterpolationBuffer buffer(pstate()); + + if (quote != 0) { + buffer.write(quote); + } + + for (auto value : text_->elements()) { + if (ItplString* str = value->isaItplString()) { + sass::string value(str->text()); + for (size_t i = 0; i < value.size(); i++) { + uint8_t codeUnit = value[i]; + if (isNewline(codeUnit)) { + buffer.write($backslash); + buffer.write($a); + if (i != value.size() - 1) { + uint8_t next = value[i + 1]; + if (isWhitespace(next) || isHex(next)) { + buffer.write($space); + } + } + } + else { + if (codeUnit == quote || + codeUnit == $backslash || + (escape && + codeUnit == $hash && + i < value.size() - 1 && + value[i + 1] == $lbrace)) { + buffer.write($backslash); + } + buffer.write(codeUnit); + } + + } + } + else { + buffer.add(value); + } + } + + if (quote != 0) { + buffer.write(quote); + } + + return buffer.getInterpolation(pstate()); + + } + // EO getAsInterpolation + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + MapExpression::MapExpression( + SourceSpan&& pstate) : + Expression(std::move(pstate)), + kvlist_() + {} + + // Convert to string (only for debugging) + sass::string MapExpression::toString() const + { + sass::sstream strm; + strm << Character::$lparen; + for (size_t i = 0; i < kvlist_.size(); i += 2) { + if (i != 0) strm << ", "; + strm << kvlist_[i]->toString() << ": "; + strm << kvlist_[i + 1]->toString(); + } + strm << Character::$rparen; + return strm.str(); + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + ListExpression::ListExpression( + SourceSpan&& pstate, + SassSeparator separator) : + Expression(std::move(pstate)), + separator_(separator), + hasBrackets_(false) + {} + + // Convert to string (only for debugging) + sass::string ListExpression::toString() const + { + StringVector parts; + for (auto& part : items_) { + parts.emplace_back(part->toString()); + } + return StringUtils::join(parts, + sass_list_separator(separator_)); + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + BinaryOpExpression::BinaryOpExpression( + SourceSpan&& pstate, + SassOperator operand, + SourceSpan&& opstate, + Expression* lhs, + Expression* rhs, + bool allowSlash, + bool isCalcSafeOp) : + Expression(std::move(pstate)), + operand_(operand), + opstate_(opstate), + left_(lhs), + right_(rhs), + allowsSlash_(allowSlash), + warned_(false), + isCalcSafeOp_(isCalcSafeOp) + {} + + // Convert to string (only for debugging) + sass::string BinaryOpExpression::toString() const + { + if (operand_ == SassOperator::DIV) { + sass::sstream buffer; + buffer << "math.div("; + auto left = this->left(); // Hack to make analysis work. + auto lhs = left->isaBinaryOpExpression(); + auto leftNeedsParens = lhs && sass_op_to_precedence(lhs->operand_) < sass_op_to_precedence(operand_); + if (leftNeedsParens) buffer << Character::$lparen; + buffer << left->toString(); + if (leftNeedsParens) buffer << Character::$rparen; + buffer << ", "; + auto right = this->right(); // Hack to make analysis work. + auto rhs = right->isaBinaryOpExpression(); + auto rightNeedsParens = rhs && sass_op_to_precedence(rhs->operand_) <= sass_op_to_precedence(operand_); + if (rightNeedsParens) buffer << Character::$lparen; + buffer << right->toString(); + if (rightNeedsParens) buffer << Character::$rparen; + buffer << ")"; + return buffer.str(); + } + else { + sass::sstream buffer; + auto left = this->left(); // Hack to make analysis work. + auto lhs = left->isaBinaryOpExpression(); + auto leftNeedsParens = lhs && sass_op_to_precedence(lhs->operand_) < sass_op_to_precedence(operand_); + if (leftNeedsParens) buffer << Character::$lparen; + buffer << left->toString(); + if (leftNeedsParens) buffer << Character::$rparen; + if (operand_ != SassOperator::IESEQ) buffer << Character::$space; + buffer << sass_op_separator(operand_); + if (operand_ != SassOperator::IESEQ) buffer << Character::$space; + auto right = this->right(); // Hack to make analysis work. + auto rhs = right->isaBinaryOpExpression(); + auto rightNeedsParens = rhs && sass_op_to_precedence(rhs->operand_) <= sass_op_to_precedence(operand_); + if (rightNeedsParens) buffer << Character::$lparen; + buffer << right->toString(); + if (rightNeedsParens) buffer << Character::$rparen; + return buffer.str(); + } + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + VariableExpression::VariableExpression( + SourceSpan&& pstate, + const EnvKey& name, + const sass::string& ns) : + Expression(std::move(pstate)), + name_(name), + ns_(ns) + {} + + // Convert to string (only for debugging) + sass::string VariableExpression::toString() const + { + return "$" + name_.norm(); + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + ParenthesizedExpression::ParenthesizedExpression( + SourceSpan&& pstate, + Expression* expression) : + Expression(std::move(pstate)), + expression_(expression) + {} + + // Convert to string (only for debugging) + sass::string ParenthesizedExpression::toString() const { + return "(" + expression_->toString() + ")"; + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + UnaryOpExpression::UnaryOpExpression( + SourceSpan&& pstate, + UnaryOpType optype, + ExpressionObj operand) : + Expression(std::move(pstate)), + optype_(optype), + operand_(operand) + {} + + // Convert to string (only for debugging) + sass::string UnaryOpExpression::toString() const + { + sass::sstream strm; + switch (optype_) { + case PLUS: strm << Character::$plus; break; + case MINUS: strm << Character::$minus; break; + case SLASH: strm << Character::$slash; break; + default: break; + } + if (optype_ == NOT) { + strm << Character::$space; + } + strm << operand_->toString(); + return strm.str(); + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + FunctionExpression::FunctionExpression( + SourceSpan pstate, + const sass::string& name, + CallableArguments* arguments, + const sass::string& ns) : + InvocationExpression( + std::move(pstate), + arguments), + ns_(ns), + name_(name) + {} + + // Convert to string (only for debugging) + sass::string FunctionExpression::toString() const + { + sass::sstream strm; + if (!ns_.empty()) { + strm << ns_ << "::"; + } + strm << name_; + strm << Character::$lparen; + strm << InvocationExpression::toString(); + strm << Character::$rparen; + return strm.str(); + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // Convert to string (only for debugging) + sass::string IfExpression::toString() const + { + return "if(" + InvocationExpression::toString() + ")"; + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // Convert to string (only for debugging) + sass::string InvocationExpression::toString() const + { + StringVector components; + for (auto positional : arguments_->positional()) { + components.emplace_back(positional->toString()); + } + for (auto& name : arguments_->named()) { + sass::sstream strm; + strm << name.first.norm() << ": "; + strm << name.second->toString(); + components.push_back(strm.str()); + } + if (arguments_->restArg()) { + sass::sstream strm; + strm << arguments_->restArg()->toString() << "..."; + components.push_back(strm.str()); + } + if (arguments_->kwdRest()) { + sass::sstream strm; + strm << arguments_->kwdRest()->toString() << "..."; + components.push_back(strm.str()); + } + return StringUtils::join(components, ", "); + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + +} + +///////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////// + +namespace Sass { + + bool isMathOperator(SassOperator op) + { + if (op == MUL) return true; + if (op == DIV) return true; + if (op == ADD) return true; + if (op == SUB) return true; + return false; + } + + bool SelectorExpression::isCalcSafe() + { + return false; + } + + bool BinaryOpExpression::isCalcSafe() + { + if (isMathOperator(operand())) { + return left()->isCalcSafe() + || right()->isCalcSafe(); + } + return false; + } + + bool ListExpression::isCalcSafe() + { + if (separator() != SASS_SPACE) return false; + if (hasBrackets() == true) return false; + if (size() < 2) return false; + for (auto asd : items()) { + if (!asd->isCalcSafe()) + return false; + } + return true; + } + + bool ParenthesizedExpression::isCalcSafe() + { + return expression()->isCalcSafe(); + } + + bool StringExpression::isCalcSafe() + { + if (hasQuotes_) return false; + auto& str = text()->getInitialPlain(); + if (str.size() > 0 && str[0] == '!') return false; + if (str.size() > 0 && str[0] == '#') return false; + if (str.size() > 1 && str[1] == '+') return false; + if (str.size() > 3 && str[3] == '(') return false; + // Requires a bit more testings + return true; + } + +} diff --git a/src/ast_expressions.hpp b/src/ast_expressions.hpp new file mode 100644 index 0000000000..c72440bc87 --- /dev/null +++ b/src/ast_expressions.hpp @@ -0,0 +1,755 @@ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#ifndef SASS_AST_EXPRESSIONS_HPP +#define SASS_AST_EXPRESSIONS_HPP + +// sass.hpp must go before all system headers +// to get the __EXTENSIONS__ fix on Solaris. +#include "capi_sass.hpp" + +#include "ast_values.hpp" +#include "ast_callables.hpp" +#include "environment_stack.hpp" + +namespace Sass { + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // Enum for UnaryOpExpression (value prefix) + enum UnaryOpType { PLUS, MINUS, NOT, SLASH }; + + ///////////////////////////////////////////////////////////////////////// + // The Parent Reference Expression. + ///////////////////////////////////////////////////////////////////////// + + class SelectorExpression final : public Expression + { + + public: + + // Value constructor + SelectorExpression( + SourceSpan&& pstate); + + // Expression visitor to sass values entry function + Value* accept(ExpressionVisitor* visitor) override final { + return visitor->visitSelectorExpression(this); + } + + // Return if expression can be used in calculations + bool isCalcSafe() override final; + + // Convert to string (only for debugging) + sass::string toString() const override final + { + return "&"; + } + + }; + + ///////////////////////////////////////////////////////////////////////// + // An expression that directly embeds a [Value]. This is never + // constructed by the parser. It's only used when ASTs are + // constructed dynamically, as for the `call()` function. + ///////////////////////////////////////////////////////////////////////// + + class ValueExpression final : public Expression + { + + private: + + // Value wrapped inside this expression + ADD_CONSTREF(ValueObj, value); + + public: + + // Value constructor + ValueExpression( + SourceSpan pstate, + ValueObj value); + + // Expression visitor to sass values entry function + Value* accept(ExpressionVisitor* visitor) override final { + return visitor->visitValueExpression(this); + } + + // Return if expression can be used in calculations + bool isCalcSafe() override final { return false; } + + // Convert to string (only for debugging) + sass::string toString() const override final; + + // Implement specialized up-casting method + IMPLEMENT_ISA_CASTER(ValueExpression); + }; + + ///////////////////////////////////////////////////////////////////////// + // The Null Expression. + ///////////////////////////////////////////////////////////////////////// + + class NullExpression final : public Expression + { + private: + + // Object can still hold a SourceSpan + ADD_CONSTREF(NullObj, value); + + public: + + // Value constructor + NullExpression( + SourceSpan pstate, + Null* value); + + // Expression visitor to sass values entry function + Value* accept(ExpressionVisitor* visitor) override final { + return visitor->visitNullExpression(this); + } + + // Return if expression can be used in calculations + bool isCalcSafe() override final { return false; } + + // Convert to string (only for debugging) + sass::string toString() const override final; + + // Implement specialized up-casting method + IMPLEMENT_ISA_CASTER(NullExpression); + }; + + ///////////////////////////////////////////////////////////////////////// + // The Color Expression. + ///////////////////////////////////////////////////////////////////////// + + class ColorExpression final : public Expression + { + private: + + // Color wrapped inside this expression + ADD_CONSTREF(ColorObj, value); + + public: + + // Value constructor + ColorExpression( + SourceSpan pstate, + Color* color); + + // Expression visitor to sass values entry function + Value* accept(ExpressionVisitor* visitor) override final { + return visitor->visitColorExpression(this); + } + + // Return if expression can be used in calculations + bool isCalcSafe() override final { return false; } + + // Convert to string (only for debugging) + sass::string toString() const override final; + + // Implement specialized up-casting method + IMPLEMENT_ISA_CASTER(ColorExpression); + }; + + ///////////////////////////////////////////////////////////////////////// + // The Number Expression. + ///////////////////////////////////////////////////////////////////////// + + class NumberExpression final : public Expression + { + private: + + // Number wrapped inside this expression + ADD_CONSTREF(NumberObj, value); + + public: + + // Value constructor + NumberExpression( + SourceSpan pstate, + Number* value); + + // Expression visitor to sass values entry function + Value* accept(ExpressionVisitor* visitor) override final { + return visitor->visitNumberExpression(this); + } + + // Return if expression can be used in calculations + bool isCalcSafe() override final { return true; } + + // Convert to string (only for debugging) + sass::string toString() const override final; + + // Implement specialized up-casting method + IMPLEMENT_ISA_CASTER(NumberExpression); + }; + + ///////////////////////////////////////////////////////////////////////// + // The Boolean Expression. + ///////////////////////////////////////////////////////////////////////// + + class BooleanExpression final : public Expression + { + private: + + // Boolean wrapped inside this expression + ADD_CONSTREF(BooleanObj, value); + + public: + + // Value constructor + BooleanExpression( + SourceSpan pstate, + Boolean* value); + + // Expression visitor to sass values entry function + Value* accept(ExpressionVisitor* visitor) override final { + return visitor->visitBooleanExpression(this); + } + + // Return if expression can be used in calculations + bool isCalcSafe() override final { return false; } + + // Convert to string (only for debugging) + sass::string toString() const override final; + + // Implement specialized up-casting method + IMPLEMENT_ISA_CASTER(BooleanExpression); + }; + + ///////////////////////////////////////////////////////////////////////// + // String expression holding an optionally quoted interpolation + ///////////////////////////////////////////////////////////////////////// + class StringExpression final : public Expression + { + private: + + // The interpolation forming this string + ADD_CONSTREF(InterpolationObj, text); + + // Flag whether the result must be quoted + ADD_CONSTREF(bool, hasQuotes); + + public: + + // Value constructor + StringExpression( + SourceSpan pstate, + InterpolationObj text, + bool hasQuotes = false); + + // Crutch instead of LiteralExpression + // Note: really not used very often + // ToDo: check for performance impact + StringExpression( + SourceSpan&& pstate, + sass::string&& text, + bool hasQuotes = false); + + // Interpolation that, when evaluated, produces the syntax of this string. + // Unlike [text], his doesn't resolve escapes and does include quotes for + // quoted strings. If [static] is true, this escapes any `#{` sequences in + // the string. If [quote] is passed, it uses that character as the quote mark; + // otherwise, it determines the best quote to add by looking at the string. + InterpolationObj getAsInterpolation( + bool escape = false, + uint8_t quote = 0) const; + + // Expression visitor to sass values entry function + Value* accept(ExpressionVisitor* visitor) override final { + return visitor->visitStringExpression(this); + } + + // Return if expression can be used in calculations + bool isCalcSafe() override final; + + private: + + // find best quote_mark by detecting if the string contains any single + // or double quotes. When a single quote is found, we not we want a double + // quote as quote_mark. Otherwise we check if the string contains any double + // quotes, which will trigger the use of single quotes as best quote_mark. + uint8_t findBestQuote() const; + + // Convert to string (only for debugging) + sass::string toString() const override final; + + // Implement specialized up-casting method + IMPLEMENT_ISA_CASTER(StringExpression); + }; + + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + class SupportsExpression final : public Expression + { + + // The condition itself + ADD_CONSTREF(SupportsConditionObj, condition); + + public: + + // Value constructor + SupportsExpression(SourceSpan pstate, + SupportsCondition* condition); + + // Expression visitor to sass values entry function + Value* accept(ExpressionVisitor* visitor) override final { + return visitor->visitSupportsExpression(this); + } + + // Return if expression can be used in calculations + bool isCalcSafe() override final { return false; } + + // Convert to string (only for debugging) + sass::string toString() const override final; + + // Implement specialized up-casting method + IMPLEMENT_ISA_CASTER(SupportsExpression); + }; + + ///////////////////////////////////////////////////////////////////////// + // Map expression holds an even list of key and value expressions. + ///////////////////////////////////////////////////////////////////////// + + class MapExpression final : public Expression + { + private: + + // We can't create a map with expression, since the keys are + // not yet resolved and we therefore don't know if any of them + // are duplicates or not. Therefore we store all key and value + // expressions in a vector, which must always be of even size. + ADD_CONSTREF(ExpressionVector, kvlist); + + public: + + // Value constructor + MapExpression( + SourceSpan&& pstate); + + // Append key or value. You must ensure to always call + // this method twice for every key-value pair. Otherwise + // it will result in undefined behavior! + void append(Expression* expression) { + kvlist_.emplace_back(expression); + } + + // Expression visitor to sass values entry function + Value* accept(ExpressionVisitor* visitor) override final { + return visitor->visitMapExpression(this); + } + + // Return if expression can be used in calculations + bool isCalcSafe() override final { return false; } + + // Convert to string (only for debugging) + sass::string toString() const override final; + + // Implement specialized up-casting method + IMPLEMENT_ISA_CASTER(MapExpression); + }; + + ///////////////////////////////////////////////////////////////////////// + // List expression holds a vector of value expressions. + ///////////////////////////////////////////////////////////////////////// + + class ListExpression final : public Expression + { + private: + + // The vector containing all children + ADD_CONSTREF(ExpressionVector, items); + + // The separator to use when rendering the list + ADD_CONSTREF(SassSeparator, separator); + + // Flag to wrap list in brackets + ADD_CONSTREF(bool, hasBrackets); + + public: + + // Value constructor + ListExpression( + SourceSpan&& pstate, + SassSeparator separator = SASS_UNDEF); + + // Append a single expression + void append(Expression* expression) { + items_.emplace_back(expression); + } + + // Move items into our vector (append) + void concat(ExpressionVector&& expressions) { + items_.insert(items_.end(), + std::make_move_iterator(expressions.begin()), + std::make_move_iterator(expressions.end())); + } + + // Return number of items + size_t size() const { + return items_.size(); + } + + // Return expression at position + Expression* get(size_t position) { + return items_[position]; + } + + // Expression visitor to sass values entry function + Value* accept(ExpressionVisitor* visitor) override final { + return visitor->visitListExpression(this); + } + + // Return if expression can be used in calculations + bool isCalcSafe() override final; + + // Convert to string (only for debugging) + sass::string toString() const override final; + + // Implement specialized up-casting method + IMPLEMENT_ISA_CASTER(ListExpression); + }; + + ///////////////////////////////////////////////////////////////////////// + // Arithmetic negation (logical negation is just an ordinary function call). + ///////////////////////////////////////////////////////////////////////// + + class UnaryOpExpression final : public Expression + { + private: + + // Type of unary op (minus, plus etc.) + ADD_CONSTREF(UnaryOpType, optype); + + // Operand following the unary operation + ADD_CONSTREF(ExpressionObj, operand); + + public: + + // Value constructor + UnaryOpExpression( + SourceSpan&& pstate, + UnaryOpType optype, + ExpressionObj operand); + + // Expression visitor to sass values entry function + Value* accept(ExpressionVisitor* visitor) override final { + return visitor->visitUnaryOpExpression(this); + } + + // Return if expression can be used in calculations + bool isCalcSafe() override final { return false; } + + // Convert to string (only for debugging) + sass::string toString() const override final; + + // Implement specialized up-casting method + IMPLEMENT_ISA_CASTER(UnaryOpExpression); + }; + + ///////////////////////////////////////////////////////////////////////// + // Binary expressions. Represents logical, relational, and arithmetic ops. + // Templatized to avoid large switch statements and repetitive sub-classing. + ///////////////////////////////////////////////////////////////////////// + + class BinaryOpExpression final : public Expression + { + private: + + // Operand to apply to left and right hand side + ADD_CONSTREF(SassOperator, operand); + + // Parser state for the operator + ADD_CONSTREF(SourceSpan, opstate); + + // Left hand side of the operation + ADD_CONSTREF(ExpressionObj, left); + + // Right hand side of the operation + ADD_CONSTREF(ExpressionObj, right); + + // Flag to delay divisions as necessary, since certain + // valid css settings can look like divisions to sass + // E.g. font: 12px/14px sans-serif + ADD_CONSTREF(bool, allowsSlash); + + // Flag if a warning was emitted (only emit once) + ADD_CONSTREF(bool, warned); + + // Is calculation safe (for add and sub) + ADD_CONSTREF(bool, isCalcSafeOp); + + // Obsolete: may be needed for output formats + // ADD_CONSTREF(bool, ws_before); + // ADD_CONSTREF(bool, ws_after); + + public: + + // Value constructor + BinaryOpExpression( + SourceSpan&& pstate, + SassOperator operand, + SourceSpan&& opstate, + Expression* lhs, + Expression* rhs, + bool allowSlash = false, + bool isCalcSafe = true); + + // Expression visitor to sass values entry function + Value* accept(ExpressionVisitor* visitor) override final { + return visitor->visitBinaryOpExpression(this); + } + + // Return if expression can be used in calculations + bool isCalcSafe() override final; + + // Convert to string (only for debugging) + sass::string toString() const override final; + + // Implement specialized up-casting method + IMPLEMENT_ISA_CASTER(BinaryOpExpression); + }; + + ///////////////////////////////////////////////////////////////////////// + // A lexical variable referencing an expression that + // was previously assigned to the named variable. If + // no variable by this name is found an error it thrown. + ///////////////////////////////////////////////////////////////////////// + + class VariableExpression final : public Expression + { + private: + + // The name of the variable (without the dollar sign) + ADD_CONSTREF(EnvKey, name); + + // Cached env references populated during runtime + ADD_REF(sass::vector, vidxs); + + // Optional module namespace + ADD_CONSTREF(sass::string, ns); + + public: + + // Value constructor + VariableExpression( + SourceSpan&& pstate, + const EnvKey& name, + const sass::string& ns = ""); + + // Return if variable is lexical + // Module variables are at the root + inline bool isLexical() const { + return ns_.empty(); + } + + // Expression visitor to sass values entry function + Value* accept(ExpressionVisitor* visitor) override final { + return visitor->visitVariableExpression(this); + } + + // Return if expression can be used in calculations + bool isCalcSafe() override final { return true; } + + // Convert to string (only for debugging) + sass::string toString() const override final; + + // Implement specialized up-casting method + IMPLEMENT_ISA_CASTER(VariableExpression); + }; + + ///////////////////////////////////////////////////////////////////////// + // An expression that must be wrapped in parentheses. + ///////////////////////////////////////////////////////////////////////// + + class ParenthesizedExpression final : public Expression + { + private: + + // Expression to be wrapped in parentheses + ADD_CONSTREF(ExpressionObj, expression) + + public: + + // Value constructor + ParenthesizedExpression( + SourceSpan&& pstate, + Expression* expression); + + // Expression visitor to sass values entry function + Value* accept(ExpressionVisitor* visitor) override final { + return visitor->visitParenthesizedExpression(this); + } + + // Return if expression can be used in calculations + bool isCalcSafe() override final; + + // Convert to string (only for debugging) + sass::string toString() const override final; + + // Implement specialized up-casting method + IMPLEMENT_ISA_CASTER(ParenthesizedExpression); + }; + + ///////////////////////////////////////////////////////////////////////// + // Base class for `IfExpression` and `FunctionExpression` + ///////////////////////////////////////////////////////////////////////// + + class InvocationExpression : public Expression + { + private: + + // Arguments passed to this invocation + ADD_CONSTREF(CallableArgumentsObj, arguments); + + public: + + // Value constructor + InvocationExpression(SourceSpan&& pstate, + CallableArguments* arguments) : + Expression(std::move(pstate)), + arguments_(arguments) + {} + + // Convert to string (only for debugging) + virtual sass::string toString() const override; + + // Declare up-casting methods + DECLARE_ISA_CASTER(IfExpression); + DECLARE_ISA_CASTER(FunctionExpression); + DECLARE_ISA_CASTER(ItplFnExpression); + // Implement specialized up-casting method + IMPLEMENT_ISA_CASTER(InvocationExpression); + }; + + ///////////////////////////////////////////////////////////////////////// + // A plain css function (not executed, simply rendered back in) + ///////////////////////////////////////////////////////////////////////// + + class ItplFnExpression final : public InvocationExpression + { + private: + + // Interpolation forming the function name + ADD_CONSTREF(InterpolationObj, itpl); + + // Optional module namespace + ADD_CONSTREF(sass::string, ns); + + public: + + // Value constructor + ItplFnExpression( + SourceSpan pstate, + Interpolation* itpl, + CallableArguments* args, + const sass::string& ns); + + // Expression visitor to sass values entry function + Value* accept(ExpressionVisitor* visitor) override final { + return visitor->visitItplFnExpression(this); + } + + // Return if expression can be used in calculations + bool isCalcSafe() override final { return true; } + + // Implement specialized up-casting method + IMPLEMENT_ISA_CASTER(ItplFnExpression); + }; + + ///////////////////////////////////////////////////////////////////////// + // Ternary expression to either return left or right and evaluated. + ///////////////////////////////////////////////////////////////////////// + + class IfExpression final : public InvocationExpression + { + public: + + // Value constructor + IfExpression(SourceSpan pstate, + CallableArguments* arguments) : + InvocationExpression(std::move(pstate), arguments) + {} + + // Expression visitor to sass values entry function + Value* accept(ExpressionVisitor* visitor) override final { + return visitor->visitIfExpression(this); + } + + // Return if expression can be used in calculations + bool isCalcSafe() override final { return true; } + + // Convert to string (only for debugging) + sass::string toString() const override final; + + // Implement specialized up-casting method + IMPLEMENT_ISA_CASTER(IfExpression); + }; + + ///////////////////////////////////////////////////////////////////////// + // Expression to invoke a function or if the function + // is not defined, renders as a plain css function. + ///////////////////////////////////////////////////////////////////////// + + class FunctionExpression final : public InvocationExpression + { + + // The namespace of the function being invoked, + // or `null` if it's invoked without a namespace. + ADD_CONSTREF(sass::string, ns); + + // The name of the function being invoked. If this is + // interpolated, the function will be interpreted as plain + // CSS, even if it has the same name as a Sass function. + ADD_CONSTREF(sass::string, name); + + // Stack reference to function + ADD_CONSTREF(EnvRef, fidx); + + public: + + // Value constructor + FunctionExpression(SourceSpan pstate, + const sass::string& name, + CallableArguments* arguments, + const sass::string& ns = ""); + + // Expression visitor to sass values entry function + Value* accept(ExpressionVisitor* visitor) override final { + return visitor->visitFunctionExpression(this); + } + + // Return if expression can be used in calculations + bool isCalcSafe() override final { return true; } + + // Imports are transparent for variables, functions and mixins + // We always need to create entities inside the parent scope + bool isImport() const { return fidx_.isImport (); } + + // Flag if this scope is considered internal + bool isInternal() const { return fidx_.isInternal(); } + + // Rules like `@if`, `@for` etc. are semi-global (permeable). + // Assignments directly in those can bleed to the root scope. + bool isSemiGlobal() const { return fidx_.isSemiGlobal(); } + + // Set to true once we are compiled via use or forward + // An import does load the sheet, but does not compile it + // Compiling it means hard-baking the config vars into it + bool isCompiled() const { return fidx_.isCompiled(); } + + // Convert to string (only for debugging) + sass::string toString() const override final; + + // Implement specialized up-casting method + IMPLEMENT_ISA_CASTER(FunctionExpression); + }; + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + +} + +#endif diff --git a/src/ast_fwd_decl.cpp b/src/ast_fwd_decl.cpp index 90936bfd36..afee856fad 100644 --- a/src/ast_fwd_decl.cpp +++ b/src/ast_fwd_decl.cpp @@ -1,31 +1,12 @@ -#include "ast.hpp" +#include "ast_fwd_decl.hpp" -namespace Sass { - - #define IMPLEMENT_BASE_CAST(T) \ - template<> \ - T* Cast(AST_Node* ptr) { \ - return dynamic_cast(ptr); \ - }; \ - \ - template<> \ - const T* Cast(const AST_Node* ptr) { \ - return dynamic_cast(ptr); \ - }; \ +#include "ast_css.hpp" +#include "ast_nodes.hpp" +#include "ast_values.hpp" +#include "ast_statements.hpp" +#include "ast_supports.hpp" +#include "ast_selectors.hpp" - IMPLEMENT_BASE_CAST(AST_Node) - IMPLEMENT_BASE_CAST(Expression) - IMPLEMENT_BASE_CAST(Statement) - IMPLEMENT_BASE_CAST(ParentStatement) - IMPLEMENT_BASE_CAST(PreValue) - IMPLEMENT_BASE_CAST(Value) - IMPLEMENT_BASE_CAST(Color) - IMPLEMENT_BASE_CAST(List) - IMPLEMENT_BASE_CAST(String) - IMPLEMENT_BASE_CAST(String_Constant) - IMPLEMENT_BASE_CAST(SupportsCondition) - IMPLEMENT_BASE_CAST(Selector) - IMPLEMENT_BASE_CAST(SelectorComponent) - IMPLEMENT_BASE_CAST(SimpleSelector) +namespace Sass { } diff --git a/src/ast_fwd_decl.hpp b/src/ast_fwd_decl.hpp index 0d5b7975f7..dda97c88e0 100644 --- a/src/ast_fwd_decl.hpp +++ b/src/ast_fwd_decl.hpp @@ -1,109 +1,183 @@ -#ifndef SASS_AST_FWD_DECL_H -#define SASS_AST_FWD_DECL_H +#ifndef SASS_AST_FWD_DECL_HPP +#define SASS_AST_FWD_DECL_HPP -// sass.hpp must go before all system headers to get the -// __EXTENSIONS__ fix on Solaris. -#include "sass.hpp" -#include "memory.hpp" -#include "sass/functions.h" +// sass.hpp must go before all system headers +// to get the __EXTENSIONS__ fix on Solaris. +#include "capi_sass.hpp" + +#include +#include +#include +#include +#include +#include "sass/values.h" +#include "shared_ptr.hpp" ///////////////////////////////////////////// // Forward declarations for the AST visitors. ///////////////////////////////////////////// namespace Sass { + // Forward declare + class EnvKey; + class EnvRef; + class EnvRefs; + class EnvRoot; + class EnvFrame; + + class Module; + class ExtensionStore; + class Extension; + class BuiltInMod; + class WithConfig; + class OutputBuffer; + + class Logger; + class Compiler; + class EnvFrame; + class SourceData; + class Import; + class ResolvedImport; class SourceFile; - class SynthFile; - class ItplFile; - - class AST_Node; + class SourceString; + class SourceWithPath; + class SourceItpl; + + class AstNode; + class Root; + + class Callable; + class UserDefinedCallable; + class ItplFnExpression; + class SupportsExpression; + class InvocationExpression; + class IfExpression; + class ExternalCallable; + class PlainCssCallable; + class BuiltInCallables; + class BuiltInCallable; + + class ArgumentResults; + class CallableArguments; + class CallableSignature; + class CallableDeclaration; + class FunctionRule; + class IncludeRule; + class ContentBlock; + class MixinRule; class ParentStatement; + class CssParentNode; class SimpleSelector; + class SelectorNS; - class Parent_Reference; + class SelectorExpression; + class BooleanExpression; + class ColorExpression; + class NumberExpression; + class NullExpression; - class PreValue; - class Block; + class Interpolant; class Expression; class Statement; class Value; class Declaration; class StyleRule; - class Bubble; - class Trace; + + class MapExpression; + class ListExpression; + class ValueExpression; class MediaRule; + + class CssRoot; + class CssNode; + class CssString; + class CssStringList; class CssMediaRule; class CssMediaQuery; + class CssAtRule; + class CssComment; + class CssDeclaration; + class CssImport; + class CssKeyframeBlock; + class CssStyleRule; + class CssSupportsRule; class SupportsRule; class AtRule; - class Keyframe_Rule; class AtRootRule; - class Assignment; + class AssignRule; - class Import; - class Import_Stub; - class WarningRule; + class WarnRule; + + class UseRule; + class ForwardRule; + class ImportRule; + class ImportBase; + class StaticImport; + class IncludeImport; class ErrorRule; class DebugRule; - class Comment; + class LoudComment; + class SilentComment; - class If; + class IfRule; class ForRule; class EachRule; class WhileRule; - class Return; - class Content; + class ReturnRule; + class ContentRule; class ExtendRule; - class Definition; class List; + class ArgumentList; class Map; class Function; - class Mixin_Call; - class Binary_Expression; - class Unary_Expression; - class Function_Call; - class Custom_Warning; - class Custom_Error; - - class Variable; + class ParenthesizedExpression; + class BinaryOpExpression; + class UnaryOpExpression; + class FunctionExpression; + class IfExpression; + class Calculation; + class Mixin; + class CalcOperation; + class CustomWarning; + class CustomError; + + class VariableExpression; class Number; class Color; - class Color_RGBA; - class Color_HSLA; + class ColorRgba; + class ColorHsla; + class ColorHwba; class Boolean; - class String; class Null; - class String_Schema; - class String_Constant; - class String_Quoted; + class Interpolation; + class ItplString; + class StringExpression; + + class String; - class Media_Query; - class Media_Query_Expression; class SupportsCondition; + class SupportsFunction; + class SupportsAnything; class SupportsOperation; class SupportsNegation; class SupportsDeclaration; - class Supports_Interpolation; - - class At_Root_Query; - class Parameter; - class Parameters; + class SupportsInterpolation; + + class AtRootQuery; class Argument; - class Arguments; class Selector; - class Selector_Schema; class PlaceholderSelector; class TypeSelector; class ClassSelector; @@ -111,101 +185,140 @@ namespace Sass { class AttributeSelector; class PseudoSelector; - - class SelectorComponent; + + class CplxSelComponent; 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 - // Note: also add a mapping without underscore - // ToDo: move to camelCase vars in the future #define IMPL_MEM_OBJ(type) \ - typedef SharedImpl type##Obj; \ - typedef SharedImpl type##_Obj; \ + typedef SharedPtr type##Obj; + + IMPL_MEM_OBJ(Extension); + IMPL_MEM_OBJ(ExtensionStore); IMPL_MEM_OBJ(SourceData); + IMPL_MEM_OBJ(Import); IMPL_MEM_OBJ(SourceFile); - IMPL_MEM_OBJ(SynthFile); - IMPL_MEM_OBJ(ItplFile); + IMPL_MEM_OBJ(SourceString); + IMPL_MEM_OBJ(SourceWithPath); + IMPL_MEM_OBJ(SourceItpl); - IMPL_MEM_OBJ(AST_Node); + IMPL_MEM_OBJ(AstNode); IMPL_MEM_OBJ(Statement); - IMPL_MEM_OBJ(Block); + IMPL_MEM_OBJ(Root); IMPL_MEM_OBJ(StyleRule); - IMPL_MEM_OBJ(Bubble); - IMPL_MEM_OBJ(Trace); IMPL_MEM_OBJ(MediaRule); + + IMPL_MEM_OBJ(MapExpression); + IMPL_MEM_OBJ(ListExpression); + IMPL_MEM_OBJ(ValueExpression); + + IMPL_MEM_OBJ(CssRoot); + IMPL_MEM_OBJ(CssNode); + IMPL_MEM_OBJ(CssStringList); + IMPL_MEM_OBJ(CssString); IMPL_MEM_OBJ(CssMediaRule); IMPL_MEM_OBJ(CssMediaQuery); + IMPL_MEM_OBJ(CssAtRule); + IMPL_MEM_OBJ(CssComment); + IMPL_MEM_OBJ(CssDeclaration); + IMPL_MEM_OBJ(CssImport); + IMPL_MEM_OBJ(CssKeyframeBlock); + IMPL_MEM_OBJ(CssStyleRule); + IMPL_MEM_OBJ(CssSupportsRule); + IMPL_MEM_OBJ(Callable); + IMPL_MEM_OBJ(UserDefinedCallable); + IMPL_MEM_OBJ(ItplFnExpression); + IMPL_MEM_OBJ(SupportsExpression); + IMPL_MEM_OBJ(ExternalCallable); + IMPL_MEM_OBJ(PlainCssCallable); + IMPL_MEM_OBJ(BuiltInCallable); + IMPL_MEM_OBJ(BuiltInCallables); IMPL_MEM_OBJ(SupportsRule); + IMPL_MEM_OBJ(CallableDeclaration); + IMPL_MEM_OBJ(FunctionRule); + IMPL_MEM_OBJ(IncludeRule); + IMPL_MEM_OBJ(ContentBlock); + IMPL_MEM_OBJ(MixinRule); IMPL_MEM_OBJ(AtRule); - IMPL_MEM_OBJ(Keyframe_Rule); IMPL_MEM_OBJ(AtRootRule); IMPL_MEM_OBJ(Declaration); - IMPL_MEM_OBJ(Assignment); - IMPL_MEM_OBJ(Import); - IMPL_MEM_OBJ(Import_Stub); - IMPL_MEM_OBJ(WarningRule); + IMPL_MEM_OBJ(AssignRule); + IMPL_MEM_OBJ(UseRule); + IMPL_MEM_OBJ(ForwardRule); + IMPL_MEM_OBJ(ImportRule); + IMPL_MEM_OBJ(ImportBase); + IMPL_MEM_OBJ(StaticImport); + IMPL_MEM_OBJ(IncludeImport); + IMPL_MEM_OBJ(WarnRule); IMPL_MEM_OBJ(ErrorRule); IMPL_MEM_OBJ(DebugRule); - IMPL_MEM_OBJ(Comment); - IMPL_MEM_OBJ(PreValue); + IMPL_MEM_OBJ(LoudComment); + IMPL_MEM_OBJ(SilentComment); IMPL_MEM_OBJ(ParentStatement); - IMPL_MEM_OBJ(If); + IMPL_MEM_OBJ(CssParentNode); + IMPL_MEM_OBJ(CallableArguments); + IMPL_MEM_OBJ(CallableSignature); + IMPL_MEM_OBJ(IfRule); IMPL_MEM_OBJ(ForRule); IMPL_MEM_OBJ(EachRule); IMPL_MEM_OBJ(WhileRule); - IMPL_MEM_OBJ(Return); - IMPL_MEM_OBJ(Content); + IMPL_MEM_OBJ(ReturnRule); + IMPL_MEM_OBJ(ContentRule); IMPL_MEM_OBJ(ExtendRule); - IMPL_MEM_OBJ(Definition); - IMPL_MEM_OBJ(Mixin_Call); IMPL_MEM_OBJ(Value); + IMPL_MEM_OBJ(Interpolant); IMPL_MEM_OBJ(Expression); IMPL_MEM_OBJ(List); + IMPL_MEM_OBJ(ArgumentList); IMPL_MEM_OBJ(Map); IMPL_MEM_OBJ(Function); - IMPL_MEM_OBJ(Binary_Expression); - IMPL_MEM_OBJ(Unary_Expression); - IMPL_MEM_OBJ(Function_Call); - IMPL_MEM_OBJ(Custom_Warning); - IMPL_MEM_OBJ(Custom_Error); - IMPL_MEM_OBJ(Variable); + IMPL_MEM_OBJ(ParenthesizedExpression); + IMPL_MEM_OBJ(BinaryOpExpression); + IMPL_MEM_OBJ(UnaryOpExpression); + IMPL_MEM_OBJ(FunctionExpression); + IMPL_MEM_OBJ(IfExpression); + IMPL_MEM_OBJ(Calculation); + IMPL_MEM_OBJ(CustomWarning); + IMPL_MEM_OBJ(CustomError); + IMPL_MEM_OBJ(VariableExpression); IMPL_MEM_OBJ(Number); IMPL_MEM_OBJ(Color); - IMPL_MEM_OBJ(Color_RGBA); - IMPL_MEM_OBJ(Color_HSLA); + IMPL_MEM_OBJ(ColorRgba); + IMPL_MEM_OBJ(ColorHsla); + IMPL_MEM_OBJ(ColorHwba); IMPL_MEM_OBJ(Boolean); - IMPL_MEM_OBJ(String_Schema); IMPL_MEM_OBJ(String); - IMPL_MEM_OBJ(String_Constant); - IMPL_MEM_OBJ(String_Quoted); - IMPL_MEM_OBJ(Media_Query); - IMPL_MEM_OBJ(Media_Query_Expression); + IMPL_MEM_OBJ(Interpolation); + IMPL_MEM_OBJ(ItplString); + IMPL_MEM_OBJ(StringExpression); IMPL_MEM_OBJ(SupportsCondition); + IMPL_MEM_OBJ(SupportsFunction); + IMPL_MEM_OBJ(SupportsAnything); IMPL_MEM_OBJ(SupportsOperation); IMPL_MEM_OBJ(SupportsNegation); IMPL_MEM_OBJ(SupportsDeclaration); - IMPL_MEM_OBJ(Supports_Interpolation); - IMPL_MEM_OBJ(At_Root_Query); + IMPL_MEM_OBJ(SupportsInterpolation); + IMPL_MEM_OBJ(AtRootQuery); IMPL_MEM_OBJ(Null); - IMPL_MEM_OBJ(Parent_Reference); - IMPL_MEM_OBJ(Parameter); - IMPL_MEM_OBJ(Parameters); + IMPL_MEM_OBJ(Mixin); + + IMPL_MEM_OBJ(SelectorExpression); + IMPL_MEM_OBJ(BooleanExpression); + IMPL_MEM_OBJ(ColorExpression); + IMPL_MEM_OBJ(NumberExpression); + IMPL_MEM_OBJ(NullExpression); + IMPL_MEM_OBJ(Argument); - IMPL_MEM_OBJ(Arguments); IMPL_MEM_OBJ(Selector); - IMPL_MEM_OBJ(Selector_Schema); IMPL_MEM_OBJ(SimpleSelector); + IMPL_MEM_OBJ(SelectorNS); IMPL_MEM_OBJ(PlaceholderSelector); IMPL_MEM_OBJ(TypeSelector); IMPL_MEM_OBJ(ClassSelector); @@ -213,61 +326,33 @@ namespace Sass { IMPL_MEM_OBJ(AttributeSelector); IMPL_MEM_OBJ(PseudoSelector); - IMPL_MEM_OBJ(SelectorComponent); + IMPL_MEM_OBJ(CplxSelComponent); IMPL_MEM_OBJ(SelectorCombinator); IMPL_MEM_OBJ(CompoundSelector); IMPL_MEM_OBJ(ComplexSelector); IMPL_MEM_OBJ(SelectorList); - // ########################################################################### + /////////////////////////////////////////////////////////////////////////# // some often used typedefs - // ########################################################################### - - typedef sass::vector BlockStack; - typedef sass::vector CalleeStack; - typedef sass::vector CallStack; - typedef sass::vector MediaStack; - typedef sass::vector SelectorStack; - typedef sass::vector ImporterStack; - - // only to switch implementations for testing - #define environment_map std::map - - // ########################################################################### - // explicit type conversion functions - // ########################################################################### - - template - T* Cast(AST_Node* ptr); - - template - const T* Cast(const AST_Node* ptr); - - // sometimes you know the class you want to cast to is final - // in this case a simple typeid check is faster and safe to use - - #define DECLARE_BASE_CAST(T) \ - template<> T* Cast(AST_Node* ptr); \ - template<> const T* Cast(const AST_Node* ptr); \ - - // ########################################################################### - // implement specialization for final classes - // ########################################################################### - - DECLARE_BASE_CAST(AST_Node) - DECLARE_BASE_CAST(Expression) - DECLARE_BASE_CAST(Statement) - DECLARE_BASE_CAST(ParentStatement) - 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(SupportsCondition) - DECLARE_BASE_CAST(Selector) - DECLARE_BASE_CAST(SimpleSelector) - DECLARE_BASE_CAST(SelectorComponent) + /////////////////////////////////////////////////////////////////////////# + + typedef sass::vector StringVector; + typedef sass::vector CplxSelComponentVector; + typedef sass::vector SelectorCombinatorVector; + typedef sass::vector ValueVector; + typedef sass::vector CssNodeVector; + typedef sass::vector CssParentVector; + typedef sass::vector CssMediaQueryVector; + typedef sass::vector CssMediaVector; + typedef sass::vector SelectorLists; + typedef sass::vector StatementVector; + typedef sass::vector ExpressionVector; + typedef std::unordered_set StringSet; + + class Eval; + class Logger; + class Compiler; + class SourceSpan; } diff --git a/src/ast_helpers.hpp b/src/ast_helpers.hpp index da8f85fe9f..bb22bccd99 100644 --- a/src/ast_helpers.hpp +++ b/src/ast_helpers.hpp @@ -1,41 +1,42 @@ #ifndef SASS_AST_HELPERS_H #define SASS_AST_HELPERS_H -// sass.hpp must go before all system headers to get the -// __EXTENSIONS__ fix on Solaris. -#include "sass.hpp" +// sass.hpp must go before all system headers +// to get the __EXTENSIONS__ fix on Solaris. +#include "capi_sass.hpp" #include #include -#include "util_string.hpp" +#include "string_utils.hpp" namespace Sass { - // ########################################################################### - // ########################################################################### + /////////////////////////////////////////////////////////////////////////# + /////////////////////////////////////////////////////////////////////////# - // easier to search with name - const bool DELAYED = true; - - // ToDo: should this really be hardcoded + // ToDo: should this really be hard-coded // Note: most methods follow precision option const double NUMBER_EPSILON = 1e-12; // macro to test if numbers are equal within a small error margin - #define NEAR_EQUAL(lhs, rhs) std::fabs(lhs - rhs) < NUMBER_EPSILON + #define NEAR_EQUAL(lhs, rhs) ((lhs == rhs) || std::fabs(lhs - rhs) < NUMBER_EPSILON) + + // macro to test if numbers are equal within a small error margin + // will also check for the case when both numbers are infinite + #define NEAR_EQUAL_INF(lhs, rhs) ((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); + return ((size_t)ptr) >> 3; } struct PtrHash { @@ -57,11 +58,9 @@ namespace Sass { } }; - // ########################################################################### + /////////////////////////////////////////////////////////////////////////# // Implement compare and hashing operations for AST Nodes - // ########################################################################### - - // TODO: get rid of functions and use ObjEquality + /////////////////////////////////////////////////////////////////////////# template // Hash the raw pointer instead of object @@ -124,7 +123,7 @@ namespace Sass { bool PtrObjEqualityFn(const T* lhs, const T* rhs) { if (lhs == nullptr) return rhs == nullptr; else if (rhs == nullptr) return false; - else return *lhs == *rhs; + else return lhs == rhs || *lhs == *rhs; } struct PtrObjEquality { @@ -149,40 +148,16 @@ namespace Sass { } }; - // ########################################################################### - // Special compare function only for hashes. - // We need to make sure to not have objects equal that - // have different hashes. This is currently an issue, - // since `1px` is equal to `1` but have different hashes. - // This goes away once we remove unitless equality. - // ########################################################################### - - template - // Compare the objects and its hashes - bool ObjHashEqualityFn(const T& lhs, const T& rhs) { - if (lhs == nullptr) return rhs == nullptr; - else if (rhs == nullptr) return false; - else return lhs->hash() == rhs->hash(); - } - struct ObjHashEquality { - template - // Compare the objects and its contents and hashes - bool operator() (const T& lhs, const T& rhs) const { - return ObjEqualityFn(lhs, rhs) && - ObjHashEqualityFn(lhs, rhs); - } - }; - - // ########################################################################### + /////////////////////////////////////////////////////////////////////////# // Implement ordering operations for AST Nodes - // ########################################################################### + /////////////////////////////////////////////////////////////////////////# template // Compare the objects behind pointers bool PtrObjLessThanFn(const T* lhs, const T* rhs) { if (lhs == nullptr) return rhs != nullptr; else if (rhs == nullptr) return false; - else return *lhs < *rhs; + else return lhs != rhs && *lhs < *rhs; } struct PtrObjLessThan { @@ -207,9 +182,9 @@ namespace Sass { } }; - // ########################################################################### + /////////////////////////////////////////////////////////////////////////# // Some STL helper functions - // ########################################################################### + /////////////////////////////////////////////////////////////////////////# // Check if all elements are equal template isaItplString()) { + // std::cerr << "Itpl str\n"; + return str->text(); + } + else if (const String* str = first()->isaString()) { + // std::cerr << "reg str\n"; + return str->value(); + } + + return Strings::empty; + } + + // Returns the plain text before the interpolation, or the empty string. + const sass::string& Interpolation::getInitialPlain() const + { + if (empty()) return Strings::empty; + if (const ItplString* str = first()->isaItplString()) { + return str->text(); + } + return Strings::empty; + } + + // Wrap interpolation within a string expression + StringExpression* Interpolation::wrapInStringExpression() { + return SASS_MEMORY_NEW(StringExpression, pstate(), this); + } + + // Convert to string (only for debugging) + sass::string Interpolation::toString() const + { + StringVector parts; + for (auto& part : elements_) { + if (String* str = part->isaString()) { + parts.push_back(str->value()); + } + else if (ItplString* str = part->isaItplString()) { + parts.push_back(str->text()); + } + else if (Value* str = part->isaValue()) { + parts.push_back(str->inspect()); + } + else if (Expression* ex = part->isaExpression()) { + parts.push_back(ex->toString()); + } + } + return StringUtils::join(parts, ""); + } + + /////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////// + + Expression::Expression(SourceSpan&& pstate) + : Interpolant(std::move(pstate)) + {} + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // Value constructor + ImportBase::ImportBase( + const SourceSpan& pstate) : + AstNode(pstate) + {} + + // Copy constructor + ImportBase::ImportBase( + const ImportBase* ptr) : + AstNode(ptr) + {} + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + StaticImport::StaticImport( + const SourceSpan& pstate, + InterpolationObj url, + InterpolationObj modifiers, + bool atRoot) : + ImportBase(pstate), + url_(url), + modifiers_(modifiers), + outOfOrder_(atRoot) + {} + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + IncludeImport::IncludeImport( + const SourceSpan& pstate, + const sass::string& prev, + const sass::string& url, + Import* import) : + ImportBase(pstate), + ModRule(prev, url) + {} + + ////////////////////////////////////////////////////////////////////// + ////////////////////////////////////////////////////////////////////// + + Iterator::Iterator(Value* val, bool end) : + val(val), last(0), cur(0) + { + if (val == nullptr) { + type = NullPtrIterator; + } + else if (Map* map = val->isaMap()) { + type = MapIterator; + last = map->size(); + } + else if (List* list = val->isaList()) { + type = ListIterator; + last = list->size(); + } + else { + type = SingleIterator; + last = 1; + } + // Move to end position + if (end) cur = last; + } + + Iterator& Iterator::operator++() + { + switch (type) { + case MapIterator: + cur = std::min(cur + 1, last); + break; + case ListIterator: + cur = std::min(cur + 1, last); + break; + case SingleIterator: + cur = 1; + break; + case NullPtrIterator: + break; + } + return *this; + } + + Iterator& Iterator::operator+=(size_t offset) + { + switch (type) { + case MapIterator: + cur = std::min(cur + offset, last); + break; + case ListIterator: + cur = std::min(cur + offset, last); + break; + case SingleIterator: + cur = 1; + break; + case NullPtrIterator: + break; + } + return *this; + } + + Iterator& Iterator::operator-=(size_t offset) + { + switch (type) { + case MapIterator: + cur = std::max(cur - offset, (size_t)0); + break; + case ListIterator: + cur = std::max(cur - offset, (size_t)0); + break; + case SingleIterator: + cur = 1; + break; + case NullPtrIterator: + break; + } + return *this; + } + + Iterator Iterator::operator-(size_t offset) + { + Iterator copy(*this); + switch (type) { + case MapIterator: + copy.cur = std::max(copy.cur - offset, (size_t)0); + break; + case ListIterator: + copy.cur = std::max(copy.cur - offset, (size_t)0); + break; + case SingleIterator: + copy.cur = 1; + break; + case NullPtrIterator: + break; + } + return copy; + } + + bool Iterator::isLast() const + { + switch (type) { + case MapIterator: + return last == cur + 1; + case ListIterator: + return last == cur + 1; + case SingleIterator: + return true; + case NullPtrIterator: + return true; + } + return true; + } + + Value* Iterator::operator*() + { + switch (type) { + case MapIterator: + return static_cast(val)->getPairAsList(cur); + case ListIterator: + return static_cast(val)->get(cur); + case SingleIterator: + return val; + case NullPtrIterator: + return nullptr; + } + return nullptr; + } + + Value* Iterator::operator->() + { + return Iterator::operator*(); + } + + bool Iterator::operator==(const Iterator& other) const + { + return val == other.val && cur == other.cur; + } + + bool Iterator::operator!=(const Iterator& other) const + { + return val != other.val || cur != other.cur; + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // Standard value constructor + Value::Value(const SourceSpan& pstate) : + Interpolant(pstate), + hash_(0) + {} + + // Copy constructor + Value::Value(const Value* ptr) : + Interpolant(ptr->pstate()), + hash_(0) + {} + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // The SassScript `>` operation. + bool Value::greaterThan(Value* other, Logger& logger, const SourceSpan& pstate) const + { + callStackFrame csf(logger, pstate); + throw Exception::SassScriptException( + "Undefined operation \"" + inspect() + + " > " + other->inspect() + "\".", + logger, pstate); + } + // EO greaterThan + + // The SassScript `>=` operation. + bool Value::greaterThanOrEquals(Value* other, Logger& logger, const SourceSpan& pstate) const + { + callStackFrame csf(logger, pstate); + throw Exception::SassScriptException( + "Undefined operation \"" + inspect() + + " >= " + other->inspect() + "\".", + logger, pstate); + } + // EO greaterThanOrEquals + + // The SassScript `<` operation. + bool Value::lessThan(Value* other, Logger& logger, const SourceSpan& pstate) const + { + callStackFrame csf(logger, pstate); + throw Exception::SassScriptException( + "Undefined operation \"" + inspect() + + " < " + other->inspect() + "\".", + logger, pstate); + } + // EO lessThan + + // The SassScript `<=` operation. + bool Value::lessThanOrEquals(Value* other, Logger& logger, const SourceSpan& pstate) const + { + callStackFrame csf(logger, pstate); + throw Exception::SassScriptException( + "Undefined operation \"" + inspect() + + " <= " + other->inspect() + "\".", + logger, pstate); + } + // EO lessThanOrEquals + + // The SassScript `*` operation. + Value* Value::times(Value* other, Logger& logger, const SourceSpan& pstate) const + { + callStackFrame csf(logger, pstate); + throw Exception::SassScriptException( + "Undefined operation \"" + inspect() + + " * " + other->inspect() + "\".", + logger, pstate); + } + // EO times + + // The SassScript `%` operation. + Value* Value::modulo(Value* other, Logger& logger, const SourceSpan& pstate) const + { + callStackFrame csf(logger, pstate); + throw Exception::SassScriptException( + "Undefined operation \"" + inspect() + + " % " + other->inspect() + "\".", + logger, pstate); + } + // EO modulo + + // The SassScript `rem` operation. + Value* Value::remainder(Value* other, Logger& logger, const SourceSpan& pstate) const + { + callStackFrame csf(logger, pstate); + throw Exception::SassScriptException( + "Undefined operation \"" + inspect() + + " % " + other->inspect() + "\".", + logger, pstate); + } + // EO remainder + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // The SassScript `=` operation. + Value* Value::singleEquals(Value* other, Logger& logger, const SourceSpan& pstate) const + { + return SASS_MEMORY_NEW(String, pstate, + toCss() + "=" + other->toCss()); + } + + // The SassScript `+` operation. + Value* Value::plus(Value* other, Logger& logger, const SourceSpan& pstate) const + { + if (String* str = other->isaString()) { + sass::string text(toCss()); + return SASS_MEMORY_NEW(String, pstate, + text + str->value(), + str->hasQuotes()); + } + else if (other->isaCalculation()) { + callStackFrame csf(logger, pstate); + throw Exception::SassScriptException( + "Undefined operation \"" + inspect() + + " + " + other->inspect() + "\".", + logger, pstate); + } + else { + sass::string text(toCss()); + return SASS_MEMORY_NEW(String, pstate, + text + other->toCss()); + } + } + + // The SassScript `-` operation. + Value* Value::minus(Value* other, Logger& logger, const SourceSpan& pstate) const + { + if (other->isaCalculation()) { + callStackFrame csf(logger, pstate); + throw Exception::SassScriptException( + "Undefined operation \"" + inspect() + + " - " + other->inspect() + "\".", + logger, pstate); + } + sass::string text(toCss()); + return SASS_MEMORY_NEW(String, pstate, + text + "-" + other->toCss()); + } + + // The SassScript `/` operation. + Value* Value::dividedBy(Value* other, Logger& logger, const SourceSpan& pstate) const + { + sass::string text(toCss()); + return SASS_MEMORY_NEW(String, pstate, + text + "/" + other->toCss()); + } + + // The SassScript unary `+` operation. + Value* Value::unaryPlus(Logger& logger, const SourceSpan& pstate) const + { + return SASS_MEMORY_NEW(String, + pstate, "+" + toCss()); + } + + // The SassScript unary `-` operation. + Value* Value::unaryMinus(Logger& logger, const SourceSpan& pstate) const + { + return SASS_MEMORY_NEW(String, + pstate, "-" + toCss()); + } + + // The SassScript unary `/` operation. + Value* Value::unaryDivide(Logger& logger, const SourceSpan& pstate) const + { + return SASS_MEMORY_NEW(String, + pstate, "/" + toCss()); + } + + // The SassScript unary `not` operation. + Value* Value::unaryNot(Logger& logger, const SourceSpan& pstate) const + { + return SASS_MEMORY_NEW(Boolean, + pstate, !isTruthy()); + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // Assert and return a value or throws if incompatible + Value* Value::assertValue(Logger& logger, const sass::string& name) + { + return this; // Nothing to check here + } + + // Assert and return a color or throws if incompatible + const Color* Value::assertColor(Logger& logger, const sass::string& name) const + { + callStackFrame csf(logger, pstate()); + throw Exception::SassScriptException( + inspect() + " is not a color.", + logger, pstate(), name); + } + + // Assert and return a function or throws if incompatible + Function* Value::assertFunction(Logger& logger, const sass::string& name) + { + callStackFrame csf(logger, pstate()); + throw Exception::SassScriptException( + inspect() + " is not a function reference.", + logger, pstate(), name); + } + + // Assert and return a map or throws if incompatible + Map* Value::assertMap(Logger& logger, const sass::string& name) + { + callStackFrame csf(logger, pstate()); + throw Exception::SassScriptException( + inspect() + " is not a map.", + logger, pstate(), name); + } + + // Assert and return a number or throws if incompatible + Number* Value::assertNumber(Logger& logger, const sass::string& name) + { + callStackFrame csf(logger, pstate()); + throw Exception::SassScriptException( + inspect() + " is not a number.", + logger, pstate(), name); + } + + // Assert and return a number/nullptr or throws if incompatible + Number* Value::assertNumberOrNull(Logger& logger, const sass::string& name) + { + if (this->isNull()) return nullptr; + return this->assertNumber(logger, name); + } + + // Assert and return a string or throws if incompatible + String* Value::assertString(Logger& logger, const sass::string& name) + { + callStackFrame csf(logger, pstate()); + throw Exception::SassScriptException( + inspect() + " is not a string.", + logger, pstate(), name); + } + + // Assert and return a string/nullptr or throws if incompatible + String* Value::assertStringOrNull(Logger& logger, const sass::string& name) + { + if (this->isNull()) return nullptr; + return this->assertString(logger, name); + } + + // Assert and return a map/nullptr or throws if incompatible + Map* Value::assertMapOrNull(Logger& logger, const sass::string& name) + { + if (this->isNull()) return nullptr; + return this->assertMap(logger, name); + } + + + // Assert and return an argument list or throws if incompatible + ArgumentList* Value::assertArgumentList(Logger& logger, const sass::string& name) + { + callStackFrame csf(logger, pstate()); + throw Exception::SassScriptException( + inspect() + " is not an argument list.", + logger, pstate(), name); + } + + // Assert and return a calculation value or throws if incompatible + Calculation* Value::assertCalculation(Logger& logger, const sass::string& name) + { + callStackFrame csf(logger, pstate()); + throw Exception::SassScriptException( + inspect() + " is not a calculation.", + logger, pstate(), name); + } + + // Assert and return a mixin value or throws if incompatible + Mixin* Value::assertMixin(Logger& logger, const sass::string& name) + { + callStackFrame csf(logger, pstate()); + throw Exception::SassScriptException( + inspect() + " is not a mixin reference.", + logger, pstate(), name); + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // Return normalized index for vector from overflow-able sass index + size_t Value::sassIndexToListIndex(Value* sassIndex, Logger& logger, const sass::string& name) + { + long index = sassIndex->assertNumber(logger, name) + ->assertInt(logger, name); + if (index == 0) throw Exception::SassScriptException( + "List index may not be 0.", logger, sassIndex->pstate(), name); + size_t size = lengthAsList(); + if (size < size_t(std::abs(index))) { + sass::sstream strm; + strm << "Invalid index " << index << " for a list "; + strm << "with " << size << " elements."; + throw Exception::SassScriptException( + strm.str(), logger, sassIndex->pstate(), name); + } + return index < 0 ? size + index : size_t(index) - 1; + } + + // Parses [this] as a selector list, in the same manner as the + // `selector-parse()` function. + /// + // Throws a [SassScriptException] if this isn't a type that can be parsed as a + // selector, or if parsing fails. If [allowParent] is `true`, this allows + // [ParentSelector]s. Otherwise, they're considered parse errors. + /// + // If this came from a function argument, [name] is the argument name + // (without the `$`). It's used for error reporting. + SelectorList* Value::assertSelector(Compiler& compiler, const sass::string& name, bool allowParent) const + { + callStackFrame frame(compiler, pstate()); + sass::string text(getSelectorString(compiler, name)); + SourceDataObj source = SASS_MEMORY_NEW(SourceItpl, pstate(), std::move(text)); + SelectorParser parser(compiler, source, allowParent); + return parser.parseSelectorList(); + } + + /// Parses [this] as a compound selector, in the same manner as the + /// `selector-parse()` function. + /// + /// Throws a [SassScriptException] if this isn't a type that can be parsed as a + /// selector, or if parsing fails. If [allowParent] is `true`, this allows + /// [ParentSelector]s. Otherwise, they're considered parse errors. + /// + /// If this came from a function argument, [name] is the argument name + /// (without the `$`). It's used for error reporting. + CompoundSelector* Value::assertCompoundSelector(Compiler& compiler, const sass::string& name, bool allowParent) const + { + callStackFrame frame(compiler, pstate()); + sass::string text(getSelectorString(compiler, name)); + SourceDataObj source = SASS_MEMORY_NEW(SourceItpl, pstate(), std::move(text)); + SelectorParser parser(compiler, source, allowParent); + return parser.parseCompoundSelector(); + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // Converts a `selector-parse()`-style input into a string that + // can be parsed. Returns `false` if [this] isn't a type or a + // structure that can be parsed as a selector. + bool Value::selectorStringOrNull(Logger& logger, sass::string& rv) const + { + if (const String* str = isaString()) { + rv = str->value(); + return true; + } + else if (const List* list = isaList()) { + if (list->empty()) return false; + sass::vector result; + if (list->separator() == SASS_COMMA) { + for (auto complex : list->elements()) { + List* cplxLst = complex->isaList(); + String* cplxStr = complex->isaString(); + if (cplxStr) { result.emplace_back(cplxStr->value()); } + else if (cplxLst && cplxLst->separator() == SASS_SPACE) { + sass::string string = complex->getSelectorString(logger); + if (string.empty()) return false; + result.emplace_back(string); + } + else return false; + } + } + else if (list->separator() == SASS_DIV) { + return false; + } + else { + for (auto compound : list->elements()) { + String* cmpdStr = compound->isaString(); + if (cmpdStr) result.emplace_back(cmpdStr->value()); + else return false; + } + } + rv = StringUtils::join(result, list->separator() == SASS_COMMA ? ", " : " "); + return true; + } + return false; + } + // EO selectorStringOrNull + + // Converts a `selector-parse()`-style input into a string that + // can be parsed. Throws a [SassScriptException] if [this] isn't + // a type or a structure that can be parsed as a selector. + sass::string Value::getSelectorString(Logger& logger, const sass::string& name) const + { + sass::string str; + if (selectorStringOrNull(logger, str)) { + return str; + } + throw Exception::SassScriptException( + inspect() + " is not a valid selector: it must be a string,\n" + "a list of strings, or a list of lists of strings.", + logger, pstate(), name); + } + // EO selectorStringOrNull + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + AtRootQuery::AtRootQuery( + SourceSpan&& pstate, + StringSet&& names, + bool include) : + AstNode(std::move(pstate)), + names_(std::move(names)), + include_(include) + {} + + // Whether this includes or excludes style rules. + inline bool AtRootQuery::rule() const { + return names_.count("rule") == 1; + } + + // Whether this includes or excludes media rules. + inline bool AtRootQuery::media() const { + return names_.count("media") == 1; + } + + // Whether this includes or excludes *all* rules. + inline bool AtRootQuery::all() const { + return names_.count("all") == 1; + } + + // Returns whether [this] excludes a node with the given [name]. + bool AtRootQuery::excludesName(const sass::string& name) const { + return (names_.count(name) == 1) != include(); + } + + // Returns whether [this] excludes [node]. + bool AtRootQuery::excludes(CssParentNode* node) const + { + if (all()) return !include(); + if (rule() && node->isaCssStyleRule()) return !include(); + return excludesName(node->getAtRuleName()); + } + + // Whether this excludes `@media` rules. + // Note that this takes [include] into account. + bool AtRootQuery::excludesMedia() const { + return (all() || media()) != include(); + } + + // Whether this excludes style rules. + // Note that this takes [include] into account. + bool AtRootQuery::excludesStyleRules() const { + return (all() || rule()) != include(); + } + + // Parses an at-root query from [contents]. If passed, [url] + // is the name of the file from which [contents] comes. + // Throws a [SassFormatException] if parsing fails. + AtRootQuery* AtRootQuery::parse(SourceData* source, Compiler& ctx) + { + AtRootQueryParser parser(ctx, source); + return parser.parse(); + } + + + // The default at-root query, which excludes only style rules. + AtRootQuery* AtRootQuery::defaultQuery(SourceSpan&& pstate) + { + StringSet wihtoutStyleRule; + wihtoutStyleRule.insert("rule"); + return SASS_MEMORY_NEW(AtRootQuery, std::move(pstate), + std::move(wihtoutStyleRule), false); + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + AstNode* AstNode::simplify(Logger& logger) + { + callStackFrame frame(logger, pstate()); + throw Exception::SassScriptException(logger, pstate(), + "Unexpected calculation argument " + toString()); + } + + sass::string AstNode::toString() const + { + if (auto itpl = dynamic_cast(this)) { + return itpl->toString(); + } + else if (auto value = dynamic_cast(this)) { + return value->inspect(); + } + else if (auto expression = dynamic_cast(this)) { + return expression->toString(); + } + else if (auto selector = dynamic_cast(this)) { + return selector->inspect(); + } + else if (auto value = dynamic_cast(this)) { + return value->inspecter(); + } + return str_empty; + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + +} diff --git a/src/ast_nodes.hpp b/src/ast_nodes.hpp new file mode 100644 index 0000000000..079481cd2f --- /dev/null +++ b/src/ast_nodes.hpp @@ -0,0 +1,684 @@ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#ifndef SASS_AST_NODES_HPP +#define SASS_AST_NODES_HPP + +#include "backtrace.hpp" +#include "source_span.hpp" +#include "ast_containers.hpp" +#include "visitor_value.hpp" +#include "visitor_statement.hpp" +#include "visitor_expression.hpp" +#include "environment_key.hpp" +#include "environment_cnt.hpp" + +namespace Sass { + + ///////////////////////////////////////////////////////////////////////// + // Some helpers in regard to sass value operations. + ///////////////////////////////////////////////////////////////////////// + + uint8_t sass_op_to_precedence(enum SassOperator op); + const char* sass_op_to_name(enum SassOperator op); + const char* sass_op_separator(enum SassOperator op); + const char* sass_list_separator(enum SassSeparator op); + + ///////////////////////////////////////////////////////////////////////// + // Abstract base class for all abstract syntax tree nodes. + ///////////////////////////////////////////////////////////////////////// + + class AstNode : public RefCounted + { + private: + + ADD_CONSTREF(SourceSpan, pstate); + + public: + + // Value copy constructor + AstNode(const SourceSpan& pstate) + : pstate_(pstate) + {} + + // Value move constructor + AstNode(SourceSpan&& pstate) + : pstate_(std::move(pstate)) + {} + + // Copy constructor + AstNode(const AstNode* ptr) + : pstate_(ptr->pstate_) + {} + + // Delete compare operators to make implementation more clear + // Helps us spot cases where we use undefined implementations + virtual bool operator==(const AstNode& rhs) const = delete; + virtual bool operator!=(const AstNode& rhs) const = delete; + virtual bool operator>=(const AstNode& rhs) const = delete; + virtual bool operator<=(const AstNode& rhs) const = delete; + virtual bool operator>(const AstNode& rhs) const = delete; + virtual bool operator<(const AstNode& rhs) const = delete; + + // Crutches to implement calculation + virtual AstNode* simplify(Logger& logger); + + // Convert to string (only for debugging) + sass::string toString() const; + + DECLARE_ISA_CASTER(Value); + + }; + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + class CalcItem { + + }; + + ///////////////////////////////////////////////////////////////////////// + // Abstract base class for items in interpolations. + // Must be one of ItplString, an Expression or a Value. + ///////////////////////////////////////////////////////////////////////// + + class Interpolant : public AstNode + { + public: + + // Value constructors + Interpolant(SourceSpan&& pstate); + Interpolant(const SourceSpan& pstate); + + // We know four types + enum Type { + ValueInterpolant, + LiteralInterpolant, + ExpressionInterpolant, + }; + + // Interface to be implemented + virtual Type getType() const = 0; + + // Declare up-casting methods + DECLARE_ISA_CASTER(Value); + DECLARE_ISA_CASTER(String); + DECLARE_ISA_CASTER(ItplString); + DECLARE_ISA_CASTER(Expression); + }; + // EO Interpolant + + /////////////////////////////////////////////////////////////////////// + // A native string wrapped as an interpolant + /////////////////////////////////////////////////////////////////////// + class ItplString final : public Interpolant + { + private: + + ADD_CONSTREF(sass::string, text) + + public: + + ItplString(const SourceSpan& pstate, sass::string&& text); + ItplString(const SourceSpan& pstate, const sass::string& text); + Type getType() const override final { return LiteralInterpolant; } + + // Implement final up-casting method + IMPLEMENT_ISA_CASTER(ItplString); + }; + // EO ItplString + + /////////////////////////////////////////////////////////////////////// + // Interpolation class holding a list of interpolants. + /////////////////////////////////////////////////////////////////////// + + class Interpolation final : public AstNode, + public Vectorized + { + public: + + // Value constructor + Interpolation(const SourceSpan& pstate, + Interpolant* interpolant = nullptr); + + // // If this contains no interpolated expressions, returns its text contents. + const sass::string& getPlainString() const; + + // Returns the plain text before the interpolation, or the empty string. + const sass::string& getInitialPlain() const; + + // Wrap interpolation within a string expression + StringExpression* wrapInStringExpression(); + + // Convert to string (only for debugging) + sass::string toString() const; + + }; + // EO Interpolation + + ////////////////////////////////////////////////////////////////////// + // Abstract base class for expressions. This side of the AST + // hierarchy represents elements in value contexts, which + // exist primarily to be evaluated and returned. + ////////////////////////////////////////////////////////////////////// + + class Expression : public Interpolant, + public ExpressionVisitable + { + public: + + // Value constructor + Expression(SourceSpan&& pstate); + + // C++ does not consider return type for function overloading + // Therefore we need to differentiate by the function name + // Basically the same as `ExpressionVisitable` + virtual bool isCalcSafe() = 0; + + // Needed here to avoid ambiguity from base-classes (issue seems gone)!?? + // virtual Value* accept(ExpressionVisitor* visitor) override = 0; + + // Implementation for parent Interpolant interface + Type getType() const override final { return ExpressionInterpolant; } + + virtual sass::string toString() const = 0; + + // Declare up-casting methods + DECLARE_ISA_CASTER(UnaryOpExpression); + DECLARE_ISA_CASTER(BinaryOpExpression); + DECLARE_ISA_CASTER(InvocationExpression); + DECLARE_ISA_CASTER(ParenthesizedExpression); + DECLARE_ISA_CASTER(FunctionExpression); + DECLARE_ISA_CASTER(VariableExpression); + DECLARE_ISA_CASTER(BooleanExpression); + DECLARE_ISA_CASTER(StringExpression); + DECLARE_ISA_CASTER(SupportsExpression); + DECLARE_ISA_CASTER(NumberExpression); + DECLARE_ISA_CASTER(ColorExpression); + DECLARE_ISA_CASTER(ValueExpression); + DECLARE_ISA_CASTER(NullExpression); + DECLARE_ISA_CASTER(ListExpression); + DECLARE_ISA_CASTER(MapExpression); + DECLARE_ISA_CASTER(IfExpression); + // Implement our up-casting + IMPLEMENT_ISA_CASTER(Expression); + }; + // EO Expression + + ///////////////////////////////////////////////////////////////////////// + // Abstract base class for statements. This side of the AST hierarchy + // represents elements in expansion contexts, which exist primarily to be + // rewritten and macro-expanded. + ///////////////////////////////////////////////////////////////////////// + class Statement : public AstNode, + public StatementVisitable, + public StatementVisitable + { + private: + + ADD_CONSTREF(size_t, tabs) + + public: + + // Value copy constructor + Statement(const SourceSpan& pstate) : + AstNode(pstate), + tabs_(0) + {} + + // Value move constructor + Statement(SourceSpan&& pstate) : + AstNode(std::move(pstate)), + tabs_(0) + {} + + // Copy constructor + Statement(const Statement* ptr) : + AstNode(ptr), + tabs_(ptr->tabs_) + {} + + // Interface to be implemented by content rule + virtual bool hasContent() const { return false; } + + // Needed here to avoid ambiguity from base-classes!?? + // virtual void accept(ExpressionVisitor* visitor) override = 0; + virtual Value* accept(StatementVisitor* visitor) override = 0; + virtual void accept(StatementVisitor* visitor) override = 0; + + // Declare up-casting methods + DECLARE_ISA_CASTER(StyleRule); + }; + + ////////////////////////////////////////////////////////////////////// + // Base class for all imports + ////////////////////////////////////////////////////////////////////// + + class ImportBase : public AstNode + { + public: + // Value constructor + ImportBase(const SourceSpan& pstate); + // Copy constructor + ImportBase(const ImportBase* ptr); + // Declare up-casting methods + DECLARE_ISA_CASTER(StaticImport); + DECLARE_ISA_CASTER(IncludeImport); + }; + + ////////////////////////////////////////////////////////////////////// + // Helper class to iterator over different Value types. + // Depending on the type of the Value (e.g. List vs String), + // we either want to iterate over a container or a single value. + // In order to avoid unnecessary copies, we use this iterator. + ////////////////////////////////////////////////////////////////////// + + class Iterator final + { + public: + + // We know four iterator types + enum ItType { + MapIterator, + ListIterator, + SingleIterator, + NullPtrIterator, + }; + + private: + + // The value we are iterating over + Value* val; + // The detected value/iterator type + ItType type; + // The final item to iterate to + // For null ptr this is zero, for + // single items this is 1 and for + // lists/maps the container size. + size_t last; + // The current iteration item + size_t cur; + + public: + + // Some typedefs to satisfy C++ type traits + typedef std::input_iterator_tag iterator_category; + typedef Iterator self_type; + typedef Value* value_type; + typedef Value* reference; + typedef Value* pointer; + typedef int difference_type; + + // Create iterator for start (or end) + Iterator(Value* val, bool end); + + // Copy constructor + Iterator(const Iterator& it) : + val(it.val), + type(it.type), + last(it.last), + cur(it.cur) {} + + // Dereference current item + reference operator*(); + reference operator->(); + + // Move to the next item + Iterator& operator++(); + Iterator& operator+=(size_t offset); + Iterator& operator-=(size_t offset); + Iterator operator-(size_t offset); + + // Check if it's the last item + bool isLast() const; + + // Compare operators + bool operator==(const Iterator& other) const; + bool operator!=(const Iterator& other) const; + + // Get iterators to support regular C++ loops + const Iterator& begin() const { return *this; } + Iterator end() const { return Iterator(val, true); } + + }; + // EO class Iterator + + ////////////////////////////////////////////////////////////////////// + // base class for values that support operations + ////////////////////////////////////////////////////////////////////// + + class Value : public Interpolant, + public ValueVisitable, + public ValueVisitable + { + public: + + // Needed here to avoid ambiguity from base-classes!?? + virtual void accept(ValueVisitor* visitor) override = 0; + virtual Value* accept(ValueVisitor* visitor) override = 0; + sass::string inspect(int precision = SassDefaultPrecision, bool quotes = true) const; + + // Getters to avoid need for dynamic cast (slightly faster) + Type getType() const override final { return ValueInterpolant; } + + virtual AstNode* simplify(Logger& logger) override; + + protected: + + // Hash is only calculated once and afterwards the value + // must not be mutated, which is the case with how sass + // works, although we must be a bit careful not to alter + // any value that has already been added to a set or map. + // Must create a copy if you need to alter such an object. + mutable size_t hash_; + + public: + + // Default implementation does nothing + virtual Value* cloneChildren(SASS_MEMORY_ARGS_VOID) { return this; } + + public: + + // Standard value constructor + Value(const SourceSpan& pstate); + + // Copy constructor + Value(const Value* ptr); + + // Use macro to allow location debugging + virtual Value* copy(SASS_MEMORY_ARGS bool childless = false) const { + throw std::runtime_error("Copy not implemented"); + } + + public: + + // Hash value when used as key in hash table + virtual size_t hash() const = 0; + + // Interface to be implemented by our classes + virtual enum SassValueType getTag() const = 0; + + // Whether the value will be represented in CSS as the empty string. + virtual bool isBlank() const { return false; } + + // Return the length of this item as a list + virtual size_t lengthAsList() const { return 1; } + + // Get iterators for values. We couldn't use begin and end + // since list and map already define these methods. + virtual Iterator start() { return Iterator(this, false); } + virtual Iterator stop() { return Iterator(this, true); } + + // Get the type in string format (for output) + virtual const sass::string& type() const = 0; + + // Search the position of the given value + virtual size_t indexOf(Value* value) { + return *this == *value ? 0 : sass::string::npos; + } + + // Return the list separator + virtual SassSeparator separator() const { + return SASS_UNDEF; + } + + // Check if we has comma separator + bool hasCommaSeparator() const { + return separator() == SASS_COMMA; + } + + // Check if we has space separator + bool hasSpaceSeparator() const { + return separator() == SASS_SPACE; + } + + // Check if we has space separator + bool hasSlashSeparator() const { + return separator() == SASS_DIV; + } + + // Check if we are bracketed + virtual bool hasBrackets() { + return false; + } + + // Check if it evaluates to true + virtual bool isTruthy() const { + return true; + } + + // Check if it is null + virtual bool isNull() const { + return false; + } + + // Reset delayed value + virtual Value* withoutSlash() { + return this; + } + + // The SassScript `==` operation (never throws). + virtual bool operator== (const Value& rhs) const = 0; + + // The SassScript `>` operation. + virtual bool greaterThan(Value* other, Logger& logger, const SourceSpan& pstate) const; + + // The SassScript `>=` operation. + virtual bool greaterThanOrEquals(Value* other, Logger& logger, const SourceSpan& pstate) const; + + // The SassScript `<` operation. + virtual bool lessThan(Value* other, Logger& logger, const SourceSpan& pstate) const; + + // The SassScript `<=` operation. + virtual bool lessThanOrEquals(Value* other, Logger& logger, const SourceSpan& pstate) const; + + // The SassScript `*` operation. + virtual Value* times(Value* other, Logger& logger, const SourceSpan& pstate) const; + + // The SassScript `%` operation. + virtual Value* modulo(Value* other, Logger& logger, const SourceSpan& pstate) const; + + // The SassScript `rem` operation. + virtual Value* remainder(Value* other, Logger& logger, const SourceSpan& pstate) const; + + /// The SassScript `=` operation. + virtual Value* singleEquals(Value* other, Logger& logger, const SourceSpan& pstate) const; + + /// The SassScript `+` operation. + virtual Value* plus(Value* other, Logger& logger, const SourceSpan& pstate) const; + + /// The SassScript `-` operation. + virtual Value* minus(Value* other, Logger& logger, const SourceSpan& pstate) const; + + /// The SassScript `/` operation. + virtual Value* dividedBy(Value* other, Logger& logger, const SourceSpan& pstate) const; + + /// The SassScript unary `+` operation. + virtual Value* unaryPlus(Logger& logger, const SourceSpan& pstate) const; + + /// The SassScript unary `-` operation. + virtual Value* unaryMinus(Logger& logger, const SourceSpan& pstate) const; + + /// The SassScript unary `/` operation. + virtual Value* unaryDivide(Logger& logger, const SourceSpan& pstate) const; + + /// The SassScript unary `not` operation. + virtual Value* unaryNot(Logger& logger, const SourceSpan& pstate) const; + + // Assert and return a value or throws if incompatible + virtual Value* assertValue(Logger& logger, const sass::string& name); + + // Assert and return a color or throws if incompatible + virtual const Color* assertColor(Logger& logger, const sass::string& name) const; + + // Assert and return a function or throws if incompatible + virtual Function* assertFunction(Logger& logger, const sass::string& name); + + // Assert and return a map or throws if incompatible + virtual Map* assertMap(Logger& logger, const sass::string& name); + + // Assert and return a number or throws if incompatible + virtual Number* assertNumber(Logger& logger, const sass::string& name); + + // Assert and return a number/nullptr or throws if incompatible + virtual Number* assertNumberOrNull(Logger& logger, const sass::string& name); + + // Assert and return a string or throws if incompatible + virtual String* assertString(Logger& logger, const sass::string& name); + + // Assert and return a string/nullptr or throws if incompatible + String* assertStringOrNull(Logger& logger, const sass::string& name); + + // Assert and return a string/nullptr or throws if incompatible + Map* assertMapOrNull(Logger& logger, const sass::string& name); + + // Assert and return an argument list or throws if incompatible + virtual ArgumentList* assertArgumentList(Logger& logger, const sass::string& name); + + // Assert and return a calculation value or throws if incompatible + virtual Calculation* assertCalculation(Logger& logger, const sass::string& name); + + // Assert and return a mixin value or throws if incompatible + virtual Mixin* assertMixin(Logger& logger, const sass::string& name); + + // Only used for nth sass function + // Single values act like lists with 1 item + // Doesn't allow overflow of index (throw error) + // Allows negative index but no overflow either + virtual Value* getValueAt(Value* index, Logger& logger); + + // Return normalized index for vector from overflowable sass index + size_t sassIndexToListIndex(Value* sassIndex, Logger& logger, const sass::string& name); + + /// Parses [this] as a selector list, in the same manner as the + /// `selector-parse()` function. + /// + /// Throws a [SassScriptException] if this isn't a type that can be parsed as a + /// selector, or if parsing fails. If [allowParent] is `true`, this allows + /// [ParentSelector]s. Otherwise, they're considered parse errors. + /// + /// If this came from a function argument, [name] is the argument name + /// (without the `$`). It's used for error reporting. + SelectorList* assertSelector(Compiler& ctx, const sass::string& name = Strings::empty, bool allowParent = false) const; + + /// Parses [this] as a compound selector, in the same manner as the + /// `selector-parse()` function. + /// + /// Throws a [SassScriptException] if this isn't a type that can be parsed as a + /// selector, or if parsing fails. If [allowParent] is `true`, this allows + /// [ParentSelector]s. Otherwise, they're considered parse errors. + /// + /// If this came from a function argument, [name] is the argument name + /// (without the `$`). It's used for error reporting. + CompoundSelector* assertCompoundSelector(Compiler& ctx, const sass::string& name = Strings::empty, bool allowParent = false) const; + + /// Returns a valid CSS representation of [this]. + /// + /// Throws a [SassScriptException] if [this] can't be represented in plain + /// CSS. Use [toString] instead to get a string representation even if this + /// isn't valid CSS. + /// + /// If [quote] is `false`, quoted strings are emitted without quotes. + sass::string toCss(bool quote = true) const; + + private: + + // Converts a `selector-parse()`-style input into a string that + // can be parsed. Returns `false` if [this] isn't a type or a + // structure that can be parsed as a selector. + bool selectorStringOrNull(Logger& logger, sass::string& rv) const; + + // Converts a `selector-parse()`-style input into a string that + // can be parsed. Throws a [SassScriptException] if [this] isn't + // a type or a structure that can be parsed as a selector. + sass::string getSelectorString(Logger& logger, const sass::string& name = Strings::empty) const; + + public: + + // Declare further up-casting methods + DECLARE_ISA_CASTER(Map); + DECLARE_ISA_CASTER(List); + DECLARE_ISA_CASTER(Null); + DECLARE_ISA_CASTER(Number); + DECLARE_ISA_CASTER(Color); + DECLARE_ISA_CASTER(ColorRgba); + DECLARE_ISA_CASTER(ColorHsla); + DECLARE_ISA_CASTER(ColorHwba); + DECLARE_ISA_CASTER(Boolean); + DECLARE_ISA_CASTER(Function); + DECLARE_ISA_CASTER(CustomError); + DECLARE_ISA_CASTER(CustomWarning); + DECLARE_ISA_CASTER(ArgumentList); + DECLARE_ISA_CASTER(Calculation); + DECLARE_ISA_CASTER(CalcOperation); + DECLARE_ISA_CASTER(Mixin); + // Implement final up-casting method + IMPLEMENT_ISA_CASTER(Value); + // Expose class as SassValue struct to C + CAPI_WRAPPER(Value, SassValue); + }; + + + ///////////////////////////////////////////////////////////////////////// + // A query for the `@at-root` rule. + ///////////////////////////////////////////////////////////////////////// + + class AtRootQuery final : public AstNode + { + private: + + // The names of the rules included or excluded by this query. There are + // two special names. "all" indicates that all rules are included or + // excluded, and "rule" indicates style rules are included or excluded. + ADD_CONSTREF(StringSet, names); + // Whether the query includes or excludes rules with the specified names. + ADD_CONSTREF(bool, include); + + public: + + // Value constructor + AtRootQuery( + SourceSpan&& pstate, + StringSet&& names, + bool include); + + // Whether this includes or excludes *all* rules. + bool all() const; + + // Whether this includes or excludes style rules. + bool rule() const; + + // Whether this includes or excludes media rules. + bool media() const; + + // Returns whether [this] excludes a node with the given [name]. + bool excludesName(const sass::string& name) const; + + // Returns whether [this] excludes [node]. + bool excludes(CssParentNode* node) const; + + // Whether this excludes `@media` rules. + // Note that this takes [include] into account. + bool excludesMedia() const; + + // Whether this excludes style rules. + // Note that this takes [include] into account. + bool excludesStyleRules() const; + + // Parses an at-root query from [contents]. If passed, [url] + // is the name of the file from which [contents] comes. + // Throws a [SassFormatException] if parsing fails. + static AtRootQuery* parse( + SourceData* contents, Compiler& ctx); + + // The default at-root query, which excludes only style rules. + static AtRootQuery* defaultQuery(SourceSpan&& pstate); + + }; + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + +} + +#endif diff --git a/src/ast_sel_cmp.cpp b/src/ast_sel_cmp.cpp index 10f75ecb9f..8fafb1ad20 100644 --- a/src/ast_sel_cmp.cpp +++ b/src/ast_sel_cmp.cpp @@ -1,214 +1,56 @@ -// sass.hpp must go before all system headers to get the -// __EXTENSIONS__ fix on Solaris. -#include "sass.hpp" - +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +/* This file contains all ast operator functions in one compile unit. */ +/*****************************************************************************/ #include "ast_selectors.hpp" +#include "ast_statements.hpp" +#include "callstack.hpp" namespace Sass { - /*#########################################################################*/ - // Compare against base class on right hand side - // try to find the most specialized implementation - /*#########################################################################*/ - - // Selector lists can be compared to comma lists - bool SelectorList::operator== (const Expression& rhs) const - { - if (auto l = Cast(&rhs)) { return *this == *l; } - if (auto s = Cast(&rhs)) { return *this == *s; } - if (Cast(&rhs) || Cast(&rhs)) { return false; } - throw std::runtime_error("invalid selector base classes to compare"); - } - - // Selector lists can be compared to comma lists - bool SelectorList::operator== (const Selector& rhs) const - { - if (auto sel = Cast(&rhs)) { return *this == *sel; } - if (auto sel = Cast(&rhs)) { return *this == *sel; } - if (auto sel = Cast(&rhs)) { return *this == *sel; } - if (auto sel = Cast(&rhs)) { return *this == *sel; } - if (auto list = Cast(&rhs)) { return *this == *list; } - throw std::runtime_error("invalid selector base classes to compare"); - } - - bool ComplexSelector::operator== (const Selector& rhs) const - { - if (auto sel = Cast(&rhs)) { return *this == *sel; } - if (auto sel = Cast(&rhs)) { return *sel == *this; } - if (auto sel = Cast(&rhs)) { return *this == *sel; } - if (auto sel = Cast(&rhs)) { return *this == *sel; } - throw std::runtime_error("invalid selector base classes to compare"); - } - - bool SelectorCombinator::operator== (const Selector& rhs) const - { - if (auto cpx = Cast(&rhs)) { return *this == *cpx; } - return false; - } - - bool CompoundSelector::operator== (const Selector& rhs) const - { - if (auto sel = Cast(&rhs)) { return *this == *sel; } - if (auto sel = Cast(&rhs)) { return *this == *sel; } - if (auto sel = Cast(&rhs)) { return *this == *sel; } - if (auto sel = Cast(&rhs)) { return *this == *sel; } - throw std::runtime_error("invalid selector base classes to compare"); - } - - bool SimpleSelector::operator== (const Selector& rhs) const - { - if (auto sel = Cast(&rhs)) { return *this == *sel; } - if (auto sel = Cast(&rhs)) { return *this == *sel; } - if (auto sel = Cast(&rhs)) { return *this == *sel; } - if (auto sel = Cast(&rhs)) return *this == *sel; - throw std::runtime_error("invalid selector base classes to compare"); - } - - /*#########################################################################*/ - /*#########################################################################*/ - + // We want to compare selector lists position independent, so we use a Set. + // This means we either need to implement a less compare method or a hashing + // function. Given that we might compare selectors quite often the hashing + // approach has proven to be slightly faster. It has some memory overhead, + // but trades off nicely for better runtime performance. bool SelectorList::operator== (const SelectorList& rhs) const { if (&rhs == this) return true; - if (rhs.length() != length()) return false; + if (rhs.size() != size()) return false; std::unordered_set lhs_set; - lhs_set.reserve(length()); + lhs_set.reserve(size()); for (const ComplexSelectorObj& element : elements()) { lhs_set.insert(element.ptr()); } for (const ComplexSelectorObj& element : rhs.elements()) { - if (lhs_set.find(element.ptr()) == lhs_set.end()) return false; + if (lhs_set.count(element.ptr()) == 0) return false; } return true; } - - - /*#########################################################################*/ - // Compare SelectorList against all other selector types - /*#########################################################################*/ - - bool SelectorList::operator== (const ComplexSelector& rhs) const - { - // If both are empty they are equal - if (empty() && rhs.empty()) return true; - // Must have exactly one item - if (length() != 1) return false; - // Compare simple selectors - return *get(0) == rhs; - } - - bool SelectorList::operator== (const CompoundSelector& rhs) const - { - // If both are empty they are equal - if (empty() && rhs.empty()) return true; - // Must have exactly one item - if (length() != 1) return false; - // Compare simple selectors - return *get(0) == rhs; - } - - bool SelectorList::operator== (const SimpleSelector& rhs) const - { - // If both are empty they are equal - if (empty() && rhs.empty()) return true; - // Must have exactly one item - if (length() != 1) return false; - // Compare simple selectors - return *get(0) == rhs; - } - - /*#########################################################################*/ - // Compare ComplexSelector against itself - /*#########################################################################*/ - bool ComplexSelector::operator== (const ComplexSelector& rhs) const { - size_t len = length(); - size_t rlen = rhs.length(); + size_t len = size(); + size_t rlen = rhs.size(); if (len != rlen) return false; for (size_t i = 0; i < len; i += 1) { - if (*get(i) != *rhs.get(i)) return false; + if (!(*get(i) == *rhs.get(i))) return false; } return true; } - /*#########################################################################*/ - // Compare ComplexSelector against all other selector types - /*#########################################################################*/ - - bool ComplexSelector::operator== (const SelectorList& rhs) const - { - // If both are empty they are equal - if (empty() && rhs.empty()) return true; - // Must have exactly one item - if (rhs.length() != 1) return false; - // Compare complex selector - return *this == *rhs.get(0); - } - - bool ComplexSelector::operator== (const CompoundSelector& rhs) const - { - // If both are empty they are equal - if (empty() && rhs.empty()) return true; - // Must have exactly one item - if (length() != 1) return false; - // Compare compound selector - return *get(0) == rhs; - } - - bool ComplexSelector::operator== (const SimpleSelector& rhs) const - { - // If both are empty they are equal - if (empty() && rhs.empty()) return true; - // Must have exactly one item - if (length() != 1) return false; - // Compare simple selectors - return *get(0) == rhs; - } - - /*#########################################################################*/ - // Compare SelectorCombinator against itself - /*#########################################################################*/ - - bool SelectorCombinator::operator==(const SelectorCombinator& rhs) const - { - return combinator() == rhs.combinator(); - } - - /*#########################################################################*/ - // Compare SelectorCombinator against SelectorComponent - /*#########################################################################*/ - - bool SelectorCombinator::operator==(const SelectorComponent& rhs) const - { - if (const SelectorCombinator * sel = rhs.getCombinator()) { - return *this == *sel; - } - return false; - } - - bool CompoundSelector::operator==(const SelectorComponent& rhs) const - { - if (const CompoundSelector * sel = rhs.getCompound()) { - return *this == *sel; - } - return false; - } - - /*#########################################################################*/ - // Compare CompoundSelector against itself - /*#########################################################################*/ - // ToDo: Verifiy implementation - /*#########################################################################*/ + //bool SelectorCombinator::operator==(const SelectorCombinator& rhs) const + //{ + // return combinator() == rhs.combinator(); + //} bool CompoundSelector::operator== (const CompoundSelector& rhs) const { - // std::cerr << "comp vs comp\n"; if (&rhs == this) return true; - if (rhs.length() != length()) return false; + if (rhs.size() != size()) return false; std::unordered_set lhs_set; - lhs_set.reserve(length()); + lhs_set.reserve(size()); for (const SimpleSelectorObj& element : elements()) { lhs_set.insert(element.ptr()); } @@ -219,178 +61,52 @@ namespace Sass { return true; } - - /*#########################################################################*/ - // Compare CompoundSelector against all other selector types - /*#########################################################################*/ - - bool CompoundSelector::operator== (const SelectorList& rhs) const - { - // If both are empty they are equal - if (empty() && rhs.empty()) return true; - // Must have exactly one item - if (rhs.length() != 1) return false; - // Compare complex selector - return *this == *rhs.get(0); - } - - bool CompoundSelector::operator== (const ComplexSelector& rhs) const - { - // If both are empty they are equal - if (empty() && rhs.empty()) return true; - // Must have exactly one item - if (rhs.length() != 1) return false; - // Compare compound selector - return *this == *rhs.get(0); - } - - bool CompoundSelector::operator== (const SimpleSelector& rhs) const - { - // If both are empty they are equal - if (empty() && rhs.empty()) return false; - // Must have exactly one item - size_t rlen = length(); - if (rlen > 1) return false; - if (rlen == 0) return true; - // Compare simple selectors - return *get(0) < rhs; - } - - /*#########################################################################*/ - // Compare SimpleSelector against itself (upcast from abstract base) - /*#########################################################################*/ - - // DOES NOT EXIST FOR ABSTRACT BASE CLASS - - /*#########################################################################*/ - // Compare SimpleSelector against all other selector types - /*#########################################################################*/ - - bool SimpleSelector::operator== (const SelectorList& rhs) const - { - // If both are empty they are equal - if (empty() && rhs.empty()) return true; - // Must have exactly one item - if (rhs.length() != 1) return false; - // Compare complex selector - return *this == *rhs.get(0); - } - - bool SimpleSelector::operator== (const ComplexSelector& rhs) const - { - // If both are empty they are equal - if (empty() && rhs.empty()) return true; - // Must have exactly one item - if (rhs.length() != 1) return false; - // Compare compound selector - return *this == *rhs.get(0); - } - - bool SimpleSelector::operator== (const CompoundSelector& rhs) const - { - // If both are empty they are equal - if (empty() && rhs.empty()) return false; - // Must have exactly one item - if (rhs.length() != 1) return false; - // Compare simple selector - return *this == *rhs.get(0); - } - - /*#########################################################################*/ - /*#########################################################################*/ - - bool IDSelector::operator== (const SimpleSelector& rhs) const - { - auto sel = Cast(&rhs); - return sel ? *this == *sel : false; - } - - bool TypeSelector::operator== (const SimpleSelector& rhs) const - { - auto sel = Cast(&rhs); - return sel ? *this == *sel : false; - } - - bool ClassSelector::operator== (const SimpleSelector& rhs) const - { - auto sel = Cast(&rhs); - return sel ? *this == *sel : false; - } - - bool PseudoSelector::operator== (const SimpleSelector& rhs) const - { - auto sel = Cast(&rhs); - return sel ? *this == *sel : false; - } - - bool AttributeSelector::operator== (const SimpleSelector& rhs) const - { - auto sel = Cast(&rhs); - return sel ? *this == *sel : false; - } - - bool PlaceholderSelector::operator== (const SimpleSelector& rhs) const - { - auto sel = Cast(&rhs); - return sel ? *this == *sel : false; - } - - /*#########################################################################*/ - /*#########################################################################*/ - bool IDSelector::operator== (const IDSelector& rhs) const { - // ID has no namespacing + // ID has no namespace return name() == rhs.name(); } bool TypeSelector::operator== (const TypeSelector& rhs) const { - return is_ns_eq(rhs) && name() == rhs.name(); + bool a = nsMatch(rhs); + bool b = name_ == rhs.name_; + return a && b; + return nsMatch(rhs) && name() == rhs.name(); } bool ClassSelector::operator== (const ClassSelector& rhs) const { - // Class has no namespacing + // Class has no namespace return name() == rhs.name(); } bool PlaceholderSelector::operator== (const PlaceholderSelector& rhs) const { - // Placeholder has no namespacing + // Placeholder has no namespace return name() == rhs.name(); } bool AttributeSelector::operator== (const AttributeSelector& rhs) const { // smaller return, equal go on, bigger abort - if (is_ns_eq(rhs)) { - if (name() != rhs.name()) return false; - if (matcher() != rhs.matcher()) return false; - if (modifier() != rhs.modifier()) return false; - const String* lhs_val = value(); - const String* rhs_val = rhs.value(); - return PtrObjEquality()(lhs_val, rhs_val); - } - else { return false; } + return nsMatch(rhs) + && op() == rhs.op() + && name() == rhs.name() + && value() == rhs.value() + && modifier() == rhs.modifier(); } bool PseudoSelector::operator== (const PseudoSelector& rhs) const { - if (is_ns_eq(rhs)) { - if (name() != rhs.name()) return false; - if (isElement() != rhs.isElement()) return false; - const String* lhs_arg = argument(); - const String* rhs_arg = rhs.argument(); - if (!PtrObjEquality()(lhs_arg, rhs_arg)) return false; - const SelectorList* lhs_sel = selector(); - const SelectorList* rhs_sel = rhs.selector(); - return PtrObjEquality()(lhs_sel, rhs_sel); - } - else { return false; } + return nsMatch(rhs) + && name() == rhs.name() + && argument() == rhs.argument() + && isPseudoElement() == rhs.isPseudoElement() + && ObjEquality()(selector(), rhs.selector()); } - /*#########################################################################*/ - /*#########################################################################*/ + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// } diff --git a/src/ast_sel_super.cpp b/src/ast_sel_super.cpp index d27bda467d..a340138cfb 100644 --- a/src/ast_sel_super.cpp +++ b/src/ast_sel_super.cpp @@ -1,49 +1,51 @@ -// sass.hpp must go before all system headers to get the -// __EXTENSIONS__ fix on Solaris. -#include "sass.hpp" -#include "ast.hpp" +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +/* This file contains all ast superselector functions in one compile unit. */ +/*****************************************************************************/ +#include "ast_selectors.hpp" -#include "util_string.hpp" +#include "dart_helpers.hpp" namespace Sass { - // ########################################################################## - // To compare/debug against libsass you can use debugger.hpp: + ///////////////////////////////////////////////////////////////////////// + // To compare/debug dart-sass vs libsass you can use debugger.hpp: // c++: std::cerr << "result " << debug_vec(compound) << "\n"; // dart: stderr.writeln("result " + compound.toString()); - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // Returns whether [list1] is a superselector of [list2]. // That is, whether [list1] matches every element that // [list2] matches, as well as possibly additional elements. - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// bool listIsSuperslector( const sass::vector& list1, const sass::vector& list2); - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // Returns whether [complex1] is a superselector of [complex2]. // That is, whether [complex1] matches every element that // [complex2] matches, as well as possibly additional elements. - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// bool complexIsSuperselector( - const sass::vector& complex1, - const sass::vector& complex2); + const CplxSelComponentVector& complex1, + const CplxSelComponentVector& complex2); - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // Returns all pseudo selectors in [compound] that have // a selector argument, and that have the given [name]. - // ########################################################################## - sass::vector selectorPseudoNamed( - CompoundSelectorObj compound, sass::string name) + ///////////////////////////////////////////////////////////////////////// + sass::vector _selectorPseudoArgs( + const CompoundSelector* compound, const sass::string& name, bool isClass = true) { sass::vector rv; - for (SimpleSelectorObj sel : compound->elements()) { - if (PseudoSelectorObj pseudo = Cast(sel)) { - if (pseudo->isClass() && pseudo->selector()) { + for (const SimpleSelectorObj& sel : compound->elements()) { + if (const PseudoSelectorObj& pseudo = sel->isaPseudoSelector()) { + if (pseudo->isClass() == isClass && pseudo->selector()) { if (sel->name() == name) { - rv.push_back(sel); + rv.emplace_back(pseudo); } } } @@ -52,34 +54,40 @@ namespace Sass { } // EO selectorPseudoNamed - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // Returns whether [simple1] is a superselector of [simple2]. // That is, whether [simple1] matches every element that // [simple2] matches, as well as possibly additional elements. - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// bool simpleIsSuperselector( - const SimpleSelectorObj& simple1, - const SimpleSelectorObj& simple2) + const SimpleSelector* simple1, + const SimpleSelector* simple2) { + + if (simple1->isUniversal()) { + // if (!simple2->isUniversal()) return false; + return simple1->nsMatch(*simple2); + } + // If they are equal they are superselectors - if (ObjEqualityFn(simple1, simple2)) { + if (PtrObjEqualityFn(simple1, simple2)) { return true; } - // Some selector pseudoclasses can match normal selectors. - if (const PseudoSelector* pseudo = Cast(simple2)) { + // Some selector pseudo-classes can match normal selectors. + if (const PseudoSelector* pseudo = simple2->isaPseudoSelector()) { if (pseudo->selector() && isSubselectorPseudo(pseudo->normalized())) { - for (auto complex : pseudo->selector()->elements()) { - // Make sure we have exacly one items - if (complex->length() != 1) { + for (auto& complex : pseudo->selector()->elements()) { + // Make sure we have exactly one items + if (complex->size() != 1) { return false; } // That items must be a compound selector - if (auto compound = Cast(complex->at(0))) { - // It must contain the lhs simple selector - if (!compound->contains(simple1)) { - return false; - } - } +// if (auto compound = complex->at(0)->isaCompoundSelector()) { +// // It must contain the lhs simple selector +// if (!compound->contains(simple1)) { +// return false; +// } +// } } return true; } @@ -88,17 +96,17 @@ namespace Sass { } // EO simpleIsSuperselector - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // Returns whether [simple] is a superselector of [compound]. // That is, whether [simple] matches every element that // [compound] matches, as well as possibly additional elements. - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// bool simpleIsSuperselectorOfCompound( - const SimpleSelectorObj& simple, - const CompoundSelectorObj& compound) + const SimpleSelector* simple, + const CompoundSelector* compound) { - for (SimpleSelectorObj simple2 : compound->elements()) { - if (simpleIsSuperselector(simple, simple2)) { + for (const SimpleSelectorObj& theirSimple : compound->elements()) { + if (simpleIsSuperselector(simple, theirSimple)) { return true; } } @@ -106,72 +114,72 @@ namespace Sass { } // EO simpleIsSuperselectorOfCompound - // ########################################################################## - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// bool typeIsSuperselectorOfCompound( - const TypeSelectorObj& type, - const CompoundSelectorObj& compound) + const TypeSelector* type, + const CompoundSelector* compound) { for (const SimpleSelectorObj& simple : compound->elements()) { - if (const TypeSelectorObj& rhs = Cast(simple)) { - if (*type != *rhs) return true; + if (const TypeSelector* rhs = simple->isaTypeSelector()) { + if (!(*type == *rhs)) return true; } } return false; } // EO typeIsSuperselectorOfCompound - // ########################################################################## - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// bool idIsSuperselectorOfCompound( - const IDSelectorObj& id, - const CompoundSelectorObj& compound) + const IDSelector* id, + const CompoundSelector* compound) { for (const SimpleSelectorObj& simple : compound->elements()) { - if (const IDSelectorObj& rhs = Cast(simple)) { - if (*id != *rhs) return true; + if (const IDSelector* rhs = simple->isaIDSelector()) { + if (!(*id == *rhs)) return true; } } return false; } // EO idIsSuperselectorOfCompound - // ########################################################################## - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// bool pseudoIsSuperselectorOfPseudo( - const PseudoSelectorObj& pseudo1, - const PseudoSelectorObj& pseudo2, + const PseudoSelector* pseudo1, + const PseudoSelector* pseudo2, const ComplexSelectorObj& parent ) { if (!pseudo2->selector()) return false; if (pseudo1->name() == pseudo2->name()) { - SelectorListObj list = pseudo2->selector(); + const SelectorList* list = pseudo2->selector(); return listIsSuperslector(list->elements(), { parent }); } return false; } // EO pseudoIsSuperselectorOfPseudo - // ########################################################################## - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// bool pseudoNotIsSuperselectorOfCompound( - const PseudoSelectorObj& pseudo1, - const CompoundSelectorObj& compound2, + const PseudoSelector* pseudo1, + const CompoundSelector* compound2, const ComplexSelectorObj& parent) { for (const SimpleSelectorObj& simple2 : compound2->elements()) { - if (const TypeSelectorObj& type2 = Cast(simple2)) { - if (const CompoundSelectorObj& compound1 = Cast(parent->last())) { + if (const TypeSelectorObj& type2 = simple2->isaTypeSelector()) { + if (const CompoundSelector* compound1 = parent->last()->selector()->isaCompoundSelector()) { if (typeIsSuperselectorOfCompound(type2, compound1)) return true; } } - else if (const IDSelectorObj& id2 = Cast(simple2)) { - if (const CompoundSelectorObj& compound1 = Cast(parent->last())) { + else if (const IDSelector* id2 = simple2->isaIDSelector()) { + if (const CompoundSelector* compound1 = parent->last()->selector()->isaCompoundSelector()) { if (idIsSuperselectorOfCompound(id2, compound1)) return true; } } - else if (const PseudoSelectorObj& pseudo2 = Cast(simple2)) { + else if (const PseudoSelector* pseudo2 = simple2->isaPseudoSelector()) { if (pseudoIsSuperselectorOfPseudo(pseudo1, pseudo2, parent)) return true; } } @@ -179,7 +187,7 @@ namespace Sass { } // pseudoNotIsSuperselectorOfCompound - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // Returns whether [pseudo1] is a superselector of [compound2]. // That is, whether [pseudo1] matches every element that [compound2] // matches, as well as possibly additional elements. This assumes that @@ -187,72 +195,107 @@ namespace Sass { // it represents the parents of [compound2]. This is relevant for pseudo // selectors with selector arguments, where we may need to know if the // parent selectors in the selector argument match [parents]. - // ########################################################################## - bool selectorPseudoIsSuperselector( - const PseudoSelectorObj& pseudo1, - const CompoundSelectorObj& compound2, + ///////////////////////////////////////////////////////////////////////// + bool _selectorPseudoIsSuperselector( + const PseudoSelector* pseudo1, + const CompoundSelector* compound2, // ToDo: is this really the most convenient way to do this? - sass::vector::const_iterator parents_from, - sass::vector::const_iterator parents_to) + CplxSelComponentVector::const_iterator parents_from, + CplxSelComponentVector::const_iterator parents_to) { + auto selector1 = pseudo1->selector(); + + if (selector1 == nullptr) { + throw ("Selector $pseudo1 must have a selector argument."); + } + // ToDo: move normalization function - sass::string name(Util::unvendor(pseudo1->name())); + sass::string name(StringUtils::unvendor(pseudo1->normalized())); + + if (name == "is" || name == "matches" || name == "any" || name == "where") { - if (name == "matches" || name == "any") { sass::vector pseudos = - selectorPseudoNamed(compound2, pseudo1->name()); - SelectorListObj selector1 = pseudo1->selector(); - for (PseudoSelectorObj pseudo2 : pseudos) { - SelectorListObj selector = pseudo2->selector(); - if (selector1->isSuperselectorOf(selector)) { + _selectorPseudoArgs(compound2, pseudo1->name()); + + for (auto selector2 : pseudos) { + if (selector1->isSuperselectorOf(selector2->selector())) { + // std::cerr << ("---- true1\n"); return true; } } - - for (ComplexSelectorObj complex1 : selector1->elements()) { - sass::vector parents; - for (auto cur = parents_from; cur != parents_to; cur++) { - parents.push_back(*cur); - } - parents.push_back(compound2); + for (auto complex1 : selector1->elements()) { + if (!complex1->leadingCombinators().empty()) continue; + CplxSelComponentVector parents(parents_from, parents_to); + parents.push_back(const_cast(compound2)->wrapInComponent({})); if (complexIsSuperselector(complex1->elements(), parents)) { + // std::cerr << ("---- true2\n"); return true; } } + // std::cerr << ("---- false\n"); + return false; + + //return pseudos.any((selector2) = > selector1.isSuperselector(selector2)) || + // selector1.components.any((complex1) = > + // complex1.leadingCombinators.isEmpty && + // complexIsSuperselector(complex1.components, [ + // ... ? parents, + // ComplexSelectorComponent(compound2, const [], compound2.span) + // ])); + //const SelectorList* selector1 = pseudo1->selector(); + //for (PseudoSelectorObj pseudo2 : pseudos) { + // const SelectorList* selector = pseudo2->selector(); + // if (selector1->isSuperselectorOf(selector)) { + // return true; + // } + //} + + for (ComplexSelectorObj complex1 : selector1->elements()) { +// CplxSelComponentVector parents; +// for (auto cur = parents_from; cur != parents_to; cur++) { +// parents.emplace_back(*cur); +// } +// parents.push_back(const_cast(compound2)); +// if (complexIsSuperselector(complex1->elements(), parents)) { +// return true; +// } + } + } - else if (name == "has" || name == "host" || name == "host-context" || name == "slotted") { + else if (name == "has" || name == "host" || name == "host-context") { sass::vector pseudos = - selectorPseudoNamed(compound2, pseudo1->name()); - SelectorListObj selector1 = pseudo1->selector(); - for (PseudoSelectorObj pseudo2 : pseudos) { - SelectorListObj selector = pseudo2->selector(); + _selectorPseudoArgs(compound2, pseudo1->name(), name != "slotted"); + const SelectorList* selector1 = pseudo1->selector(); + for (const PseudoSelector* pseudo2 : pseudos) { + const SelectorList* selector = pseudo2->selector(); if (selector1->isSuperselectorOf(selector)) { return true; } } - } else if (name == "not") { - for (ComplexSelectorObj complex : pseudo1->selector()->elements()) { + + for (const ComplexSelectorObj& complex : pseudo1->selector()->elements()) { if (!pseudoNotIsSuperselectorOfCompound(pseudo1, compound2, complex)) return false; } return true; + } else if (name == "current") { sass::vector pseudos = - selectorPseudoNamed(compound2, "current"); - for (PseudoSelectorObj pseudo2 : pseudos) { - if (ObjEqualityFn(pseudo1, pseudo2)) return true; + _selectorPseudoArgs(compound2, pseudo1->name()); + for (const PseudoSelector* pseudo2 : pseudos) { + if (PtrObjEqualityFn(pseudo1, pseudo2)) return true; } } else if (name == "nth-child" || name == "nth-last-child") { for (auto simple2 : compound2->elements()) { - if (PseudoSelectorObj pseudo2 = simple2->getPseudoSelector()) { + if (const PseudoSelector* pseudo2 = simple2->isaPseudoSelector()) { if (pseudo1->name() != pseudo2->name()) continue; - if (!ObjEqualityFn(pseudo1->argument(), pseudo2->argument())) continue; + if (pseudo1->argument() != pseudo2->argument()) continue; if (pseudo1->selector()->isSuperselectorOf(pseudo2->selector())) return true; } } @@ -264,27 +307,92 @@ namespace Sass { } // EO selectorPseudoIsSuperselector - // ########################################################################## + /// If [compound] contains a pseudo-element, returns it and its index in +/// [compound.components]. + PseudoSelector* _findPseudoElementIndexed(const CompoundSelector* compound, size_t& n) + { + for (size_t i = 0; i < compound->elements().size(); i++) { + auto simple = compound->elements()[i]; + if (auto pseudo = simple->isaPseudoSelector()) { + if (pseudo->isElement()) { + n = i; return pseudo; + } + } + } + return nullptr; + } + + /// Like [compoundIsSuperselector] but operates on the underlying lists of +/// simple selectors. +/// +/// The [compound1] and [compound2] are expected to have efficient +/// [Iterable.length] fields. + bool _compoundComponentsIsSuperselector( + sass::vector compound1, + sass::vector compound2, + sass::vector parents) + { + if (compound1.empty()) return true; + if (compound2.empty()) { + sass::string name("*"); + sass::string ns("*"); + compound2.push_back( + new TypeSelector( + SourceSpan::internal("FAKE"), + std::move(name), + std::move(ns))); + } + auto bogus = SourceSpan::internal("FAKE"); + return compoundIsSuperselector( + new CompoundSelector(bogus, std::move(compound1)), + new CompoundSelector(bogus, std::move(compound2)), + parents); + } + + + + ///////////////////////////////////////////////////////////////////////// // Returns whether [compound1] is a superselector of [compound2]. // That is, whether [compound1] matches every element that [compound2] // matches, as well as possibly additional elements. If [parents] is // passed, it represents the parents of [compound2]. This is relevant // for pseudo selectors with selector arguments, where we may need to // know if the parent selectors in the selector argument match [parents]. - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// + bool compoundIsSuperselector( - const CompoundSelectorObj& compound1, - const CompoundSelectorObj& compound2, + const CompoundSelector* compound1, + const CompoundSelector* compound2, // ToDo: is this really the most convenient way to do this? - const sass::vector::const_iterator parents_from, - const sass::vector::const_iterator parents_to) + const CplxSelComponentVector::const_iterator parents_from, + const CplxSelComponentVector::const_iterator parents_to) { + /* + size_t n1; size_t n2; + auto pseudo1 = _findPseudoElementIndexed(compound1, n1); + auto pseudo2 = _findPseudoElementIndexed(compound2, n2); + + if (pseudo1 && pseudo2) { + pseudo1->isSuperselector + // return pseudo1->isSuperselector(pseudo2) && + // _compoundComponentsIsSuperselector(compound1.components.take(index1), + // compound2.components.take(index2), parents: parents) && + // _compoundComponentsIsSuperselector( + // compound1.components.skip(index1 + 1), + // compound2.components.skip(index2 + 1), + // parents: parents); + } + else if (pseudo1 || pseudo2) { + return false; + } + */ + // Every selector in [compound1.components] must have // a matching selector in [compound2.components]. - for (SimpleSelectorObj simple1 : compound1->elements()) { - PseudoSelectorObj pseudo1 = Cast(simple1); + for (const SimpleSelector* simple1 : compound1->elements()) { + const PseudoSelector* pseudo1 = simple1->isaPseudoSelector(); if (pseudo1 && pseudo1->selector()) { - if (!selectorPseudoIsSuperselector(pseudo1, compound2, parents_from, parents_to)) { + if (!_selectorPseudoIsSuperselector(pseudo1, compound2, parents_from, parents_to)) { return false; } } @@ -294,9 +402,9 @@ namespace Sass { } // [compound1] can't be a superselector of a selector // with pseudo-elements that [compound2] doesn't share. - for (SimpleSelectorObj simple2 : compound2->elements()) { - PseudoSelectorObj pseudo2 = Cast(simple2); - if (pseudo2 && pseudo2->isElement()) { + for (const SimpleSelector* simple2 : compound2->elements()) { + const PseudoSelector* pseudo2 = simple2->isaPseudoSelector(); + if (pseudo2 && pseudo2->isPseudoElement() && pseudo2->selector() == nullptr) { if (!simpleIsSuperselectorOfCompound(pseudo2, compound1)) { return false; } @@ -306,147 +414,314 @@ namespace Sass { } // EO compoundIsSuperselector - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // Returns whether [compound1] is a superselector of [compound2]. // That is, whether [compound1] matches every element that [compound2] // matches, as well as possibly additional elements. If [parents] is // passed, it represents the parents of [compound2]. This is relevant // for pseudo selectors with selector arguments, where we may need to // know if the parent selectors in the selector argument match [parents]. - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// bool compoundIsSuperselector( - const CompoundSelectorObj& compound1, - const CompoundSelectorObj& compound2, - const sass::vector& parents) + const CompoundSelector* compound1, + const CompoundSelector* compound2, + const CplxSelComponentVector& parents) { - return compoundIsSuperselector( - compound1, compound2, - parents.begin(), parents.end() - ); + + size_t n1; size_t n2; + auto pseudo1 = _findPseudoElementIndexed(compound1, n1); + auto pseudo2 = _findPseudoElementIndexed(compound2, n2); + + if (pseudo1 && pseudo2) { + + // std::cerr << "both are pseudo\n"; + if (pseudo1->isSuperselectorAF(pseudo2)) { + // std::cerr << "First condition is true\n"; + auto l1 = sass::vector(compound1->begin(), compound1->begin() + n1); + auto l2 = sass::vector(compound2->begin(), compound2->begin() + n2); + if (!_compoundComponentsIsSuperselector(l1, l2, parents)) return false; + // std::cerr << "Second condition is true\n"; + auto e1 = sass::vector(compound1->begin() + n1 + 1, compound1->end()); + auto e2 = sass::vector(compound2->begin() + n2 + 1, compound2->end()); + return _compoundComponentsIsSuperselector(e1, e2, parents); + } + } + else if (pseudo1 || pseudo2) { + return false; + } + + // Every selector in [compound1.components] must have a matching selector in +// [compound2.components]. + for (auto simple1 : compound1->elements()) { + // std::cerr << "Go check " << simple1->inspect() << "\n"; + // if (simple1 case PseudoSelector(selector: _ ? )) { + auto pseudo = simple1->isaPseudoSelector(); + if (pseudo && pseudo->selector() != nullptr) { + // std::cerr << "-- Check another pseudo inner\n"; + if (!_selectorPseudoIsSuperselector(pseudo, compound2, + parents.begin(), parents.end())) { + return false; + } + } + else { + // std::cerr << "Check via simple.isSuperselector\n"; + bool any = false; + // std::cerr << "+++++++++ has any " << simple1->inspect() << "\n"; + for (auto& s2 : compound2->elements()) { + if (simple1->isSuperselectorAF(s2)) { + any = true; + break; + } + } + if (!any) { + // std::cerr << "Has a non super selector\n"; + return false; + } + // std::cerr << "Checked via simple.isSUper\n"; + } + } + + return true; } // EO compoundIsSuperselector - // ########################################################################## + + + bool SimpleSelector::isSuperselector(SimpleSelector* other) const + { + // std::cerr << "Simple::isSuper\n"; + // Check if matching dart + return simpleIsSuperselector(this, other); + } + + + bool _compatibleWithPreviousCombinator(SelectorCombinator* previous, + const CplxSelComponentVector& parents) + { + if (parents.empty()) return true; + if (previous == nullptr) return true; + + // The child and next sibling combinators require that the *immediate* + // following component be a superslector. + if (!previous->isFollowingSibling()) return false; + + // The following sibling combinator does allow intermediate components, but + // only if they're all siblings. + + for (auto& component : parents) { + if (!component->combinators().empty()) { + auto first = component->combinators().front(); + if (first->isFollowingSibling()) continue; + if (first->isNextSibling()) continue; + } + return false; + } + + return true; + } + + /// Returns whether [combinator1] is a supercombinator of [combinator2]. +/// +/// That is, whether `X combinator1 Y` is a superselector of `X combinator2 Y`. + bool _isSupercombinator2( + SelectorCombinator* combinator1, + SelectorCombinator* combinator2) + { + if (combinator1 == nullptr) return !combinator2 || combinator2->isChild(); + if (combinator2 == nullptr) return false; + auto qwe = combinator1->combinator() == combinator2->combinator() || + (combinator1 == nullptr && combinator2->isChild()) || + (combinator1->isFollowingSibling() && + combinator2->isNextSibling()); + return qwe; + } + template + sass::string ToString(T* asd) { + if (asd == nullptr) return "nullptr"; + return asd->toString(); + } + + bool _isSupercombinator( + SelectorCombinator* combinator1, + SelectorCombinator* combinator2) + { + auto qwe = _isSupercombinator2(combinator1, combinator2); + // std::cerr << "isSupercombi " << ToString(combinator1) << " vs " << ToString(combinator2) << " => " << qwe << "\n"; + return qwe; + } + + + + template + T frontOrNull(sass::vector asd) { + if (asd.size() == 0) return nullptr; + return asd.front(); + } + + ///////////////////////////////////////////////////////////////////////// // Returns whether [complex1] is a superselector of [complex2]. // That is, whether [complex1] matches every element that // [complex2] matches, as well as possibly additional elements. - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// bool complexIsSuperselector( - const sass::vector& complex1, - const sass::vector& complex2) + const CplxSelComponentVector& complex1, + const CplxSelComponentVector& 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; + // ADD AGAIN + + //std::cerr << "complexIsSuper1 " << InspectVector(complex1) << "\n"; + //std::cerr << "complexIsSuper2 " << InspectVector(complex2) << "\n"; + + if (complex1.size() == 2 && complex2.size() == 3) { + } + // complexIsSuper1[b, % ] + // complexIsSuper2[a + , b, % ] + + // std::cerr << "# EXEC complexIsSuperselector\n"; + + if (complex1.empty()) return false; + if (complex2.empty()) return false; - size_t i1 = 0, i2 = 0; + if (!complex1.back()->combinators().empty()) return false; + if (!complex2.back()->combinators().empty()) return false; + + size_t i1 = 0; + size_t i2 = 0; + + SelectorCombinator* previousCombinator = nullptr; while (true) { + // std::cerr << "Loop " << i1 << " " << i2 << "\n"; size_t remaining1 = complex1.size() - i1; size_t remaining2 = complex2.size() - i2; + // std::cerr << "Remain " << i1 << " " << i2 << "\n"; if (remaining1 == 0 || remaining2 == 0) { + // std::cerr << "No more remains\n"; return false; } - // More complex selectors are never - // superselectors of less complex ones. + + // More complex selectors are never superselectors of less complex ones. if (remaining1 > remaining2) { + // std::cerr << "More complex\n"; return false; } - // Selectors with leading operators are - // neither superselectors nor subselectors. - if (Cast(complex1[i1])) { - return false; - } - if (Cast(complex2[i2])) { + auto component1 = complex1[i1]; + + if (component1->combinators().size() > 1) { + // std::cerr << "invalid combinators\n"; return false; } + if (remaining1 == 1) { - CompoundSelectorObj compound1 = Cast(complex1[i1]); - CompoundSelectorObj compound2 = Cast(complex2.back()); + CplxSelComponentVector parents( + complex2.begin() + i2, + complex2.end() - 1); - if (remaining1 == 1) { - sass::vector::const_iterator parents_to = complex2.end(); - sass::vector::const_iterator parents_from = complex2.begin(); - std::advance(parents_from, i2 + 1); // equivalent to dart `.skip(i2 + 1)` - bool rv = compoundIsSuperselector(compound1, compound2, parents_from, parents_to); - sass::vector pp; - - sass::vector::const_iterator end = parents_to; - sass::vector::const_iterator beg = parents_from; - while (beg != end) { - pp.push_back(*beg); - beg++; + for (auto p : parents) { + if (p->combinators().size() > 1) { + // std::cerr << "invalid parent\n"; + return false; + } } + // std::cerr << "+ check comp super1 " << component1->selector()->inspect() << " vs " << complex2.back()->selector()->inspect() << "\n"; + + auto rv = compoundIsSuperselector( + component1->selector(), + complex2.back()->selector(), + parents); + // stderr.write("Determined ${rv}\n"); 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)) { - sass::vector::const_iterator parents_to = complex2.begin(); - sass::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; - } + + // Find the first index [endOfSubselector] in [complex2] such that +// `complex2.sublist(i2, endOfSubselector + 1)` is a subselector of +// [component1.selector]. + auto endOfSubselector = i2; + CplxSelComponentVector parents; // nullable? + while (true && endOfSubselector < complex2.size()) { + // std::cerr << "Get from complex2 size " << complex2.size() << " at " << endOfSubselector << "\n"; + auto component2 = complex2[endOfSubselector]; + if (component2->combinators().size() > 1) return false; + + // std::cerr << "+ check comp super2 " << + // component1->selector()->inspect() << " vs " << + // component2->selector()->inspect() << "\n"; + + if (compoundIsSuperselector(component1->selector(), component2->selector(), parents)) { + // std::cerr << "-- breakup\n"; + break; } + + endOfSubselector++; + if (endOfSubselector == complex2.size() - 1) { + // std::cerr << "End of subselector\n"; + // 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. + return false; + } + + // parents.clear(); + parents.push_back(component2); } - if (afterSuperselector == complex2.size()) { + + + if (!_compatibleWithPreviousCombinator( + previousCombinator, parents)) { + // std::cerr << ("Incompatible with previous\n"); return false; } - SelectorComponentObj component1 = complex1[i1 + 1], - component2 = complex2[afterSuperselector]; + if (complex2.size() <= endOfSubselector) { + // std::cerr << "End of subselector 2\n"; + break; + } + auto component2 = complex2[endOfSubselector]; + auto combinator1 = frontOrNull(component1->combinators()); + auto combinator2 = frontOrNull(component2->combinators()); - SelectorCombinatorObj combinator1 = Cast(component1); - SelectorCombinatorObj combinator2 = Cast(component2); + // std::cerr << "test " << ToString(combinator1.ptr()) << " vs " << ToString(combinator2.ptr()) << "\n"; - if (!combinator1.isNull()) { + // stderr.write("test ${combinator1} vs ${combinator2}\n"); + if (!_isSupercombinator(combinator1, combinator2)) { + // std::cerr << "Not a supercombinator1\n"; + return false; + } - if (combinator2.isNull()) { - return false; + + i1++; + i2 = endOfSubselector + 1; + previousCombinator = combinator1; + + if (complex1.size() - i1 == 1) { + if (combinator1 && combinator1->isFollowingSibling()) { + // stderr.write("Has fallowing sibling\n"); + // The selector `.foo ~ .bar` is only a superselector of selectors that + // *exclusively* contain subcombinators of `~`. + // bool isEverySuper = true; + for (size_t i3 = i2; i3 < complex2.size() - 1; i3++) { + auto component = complex2[i3]; + if (!_isSupercombinator(combinator1, component->combinators().front())) { + // std::cerr << "Not a supercombinator2\n"; + return false; + } + } } - // `.a ~ .b` is a superselector of `.a + .b`, - // but otherwise the combinators must match. - if (combinator1->isGeneralCombinator()) { - if (combinator2->isChildCombinator()) { + else if (combinator1 != nullptr) { + //stderr.write("has other combinator\n"); + // `.foo > .bar` and `.foo + bar` aren't superselectors of any selectors + // with more than one combinator. + if (complex2.size() - i2 > 1) { + // std::cerr << "End of subselector 3\n"; 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; } } @@ -455,45 +730,56 @@ namespace Sass { } // EO complexIsSuperselector - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // Like [complexIsSuperselector], but compares [complex1] // and [complex2] as though they shared an implicit base // [SimpleSelector]. For example, `B` is not normally a // superselector of `B A`, since it doesn't match elements // that match `A`. However, it *is* a parent superselector, // since `B X` is a superselector of `B A X`. - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// bool complexIsParentSuperselector( - const sass::vector& complex1, - const sass::vector& complex2) + const CplxSelComponentVector& complex1, + const CplxSelComponentVector& 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.empty() && complex2.empty()) return false; + ////if (complex1.front()->isaSelectorCombinator()) return false; + ////if (complex2.front()->isaSelectorCombinator()) return false; + //if (complex1.size() > complex2.size()) return false; + //// TODO(nweiz): There's got to be a way to do this without a bunch of extra allocations... + if (complex1.size() > complex2.size()) return false; - // TODO(nweiz): There's got to be a way to do this without a bunch of extra allocations... - sass::vector cplx1(complex1); - sass::vector cplx2(complex2); - CompoundSelectorObj base = SASS_MEMORY_NEW(CompoundSelector, "[tmp]"); - cplx1.push_back(base); cplx2.push_back(base); + //std::cerr << "complexIsParentSuper1 " << InspectVector(complex1) << "\n"; + //std::cerr << "complexIsParentSuper2 " << InspectVector(complex2) << "\n"; + + CplxSelComponentVector cplx1(complex1); + CplxSelComponentVector cplx2(complex2); + PlaceholderSelectorObj phs = SASS_MEMORY_NEW(PlaceholderSelector, + SourceSpan::internal("[BASE]"), "%"); + CplxSelComponentObj base = SASS_MEMORY_NEW(CplxSelComponent, + SourceSpan::internal("[BASE]"), {}, phs->wrapInCompound() ); + //std::cerr << "BOGUS ==> " << base->inspecter() << "\n"; + 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( sass::vector list, ComplexSelectorObj complex) { // Return true if every [complex] selector on [list2] // is a super selector of the full selector [list1]. - for (ComplexSelectorObj lhs : list) { - if (complexIsSuperselector(lhs->elements(), complex->elements())) { + for (const ComplexSelector* lhs : list) { + // if (complexIsSuperselector(lhs->elements(), complex->elements())) { + if (lhs->isSuperselectorOf(complex)) { return true; } } @@ -501,18 +787,18 @@ namespace Sass { } // listIsSuperslectorOfComplex - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // Returns whether [list1] is a superselector of [list2]. // That is, whether [list1] matches every element that // [list2] matches, as well as possibly additional elements. - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// bool listIsSuperslector( const sass::vector& list1, const sass::vector& list2) { // Return true if every [complex] selector on [list2] // is a super selector of the full selector [list1]. - for (ComplexSelectorObj complex : list2) { + for (const ComplexSelectorObj& complex : list2) { if (!listHasSuperslectorForComplex(list1, complex)) { return false; } @@ -521,19 +807,121 @@ namespace Sass { } // 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()); + + return leadingCombinators_.empty() && + sub->leadingCombinators_.empty() && + complexIsSuperselector(elements(), sub->elements()); + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + bool SimpleSelector::isSuperselectorAF(SimpleSelector* other) const + { + // std::cerr << ("Simple::isSuper\n"); + + if (PtrObjEqualityFn(this, other)) { + // std::cerr << "Stuff is equal\n"; + return true; + } + + if (auto pseudo = other->isaPseudoSelector()) { + if (pseudo->isClass()) { + // std::cerr << ("Test inner pseudo\n"); + auto list = pseudo->selector(); + if (list == nullptr) return false; + + if (isSubselectorPseudo(pseudo->normalized())) { + for (auto complex : list->elements()) { + if (complex->empty()) continue; + for (auto simple : complex->last()->selector()->elements()) { + if (!isSuperselectorAF(simple)) { + return false; + } + } + } + return true; + } + } + } + return false; + } + + bool TypeSelector::isSuperselectorAF(SimpleSelector* other) const + { + // std::cerr << "++ Type::isSuper " << this->inspect() << " vs " << other->inspect() << "\n"; + if (isUniversal()) { + // std::cerr << "UNIVERSAL\n"; + return nsMatch(*other); + } + else { + if (SimpleSelector::isSuperselectorAF(other)) { + // std::cerr << (" res1 => true\n"); + return true; + } + if (auto type = other->isaTypeSelector()) { + auto q = name_ == type->name_ && nsMatch(*type); + // std::cerr << " res2 => " << q << "\n"; + return q; + } + } + // std::cerr << (" res2 => false\n"); + return false; + } + + bool PseudoSelector::isSuperselectorAF(SimpleSelector* other) const + { + // std::cerr << ("Pseudo::isSuper\n"); + auto selector = this->selector(); + if (selector == nullptr) return PtrObjEqualityFn((SimpleSelector*)this, other); + if (auto pseudo = other->isaPseudoSelector()) { + if (isElement() && + pseudo->isElement() && + normalized_ == "slotted" && + pseudo->name_ == name_) + { + if (pseudo->selector() == nullptr) return false; + return selector->isSuperselectorOf(pseudo->selector()); + } + } + return false; + } + + bool PseudoSelector::isSuperSelector(PseudoSelector* other) const + { + if (SimpleSelector::isSuperselectorAF(other)) return true; + + auto selector = this->selector(); + if (selector == nullptr) return this == other; + if (other->isaPseudoSelector() && + isElement() && + other->isElement() && + normalized_ == "slotted" && + other->name_ == name_) + { + if (other->selector() == nullptr) return false; + return selector->isSuperselectorOf(other->selector()); + } + + // Fall back to the logic defined in functions.dart, which knows how to + // compare selector pseudoclasses against raw selectors. + //return CompoundSelector([this], span) + // .isSuperselector(CompoundSelector([other], span)); + + return false; } - // ########################################################################## - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// } diff --git a/src/ast_sel_unify.cpp b/src/ast_sel_unify.cpp index 31e151e74b..6637b8671b 100644 --- a/src/ast_sel_unify.cpp +++ b/src/ast_sel_unify.cpp @@ -1,275 +1,513 @@ -// sass.hpp must go before all system headers to get the -// __EXTENSIONS__ fix on Solaris. -#include "sass.hpp" +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +/* This file contains all ast unify functions in one compile unit. */ +/*****************************************************************************/ +#include "ast_selectors.hpp" -#include "ast.hpp" +#include "debugger.hpp" namespace Sass { - // ########################################################################## + + template + sass::string VecToString2(sass::vector exts) { + sass::string msg = "["; + bool joiner = false; + for (auto& entry : exts) { + if (joiner) msg += ", "; + msg += entry->inspect(); + joiner = true; + } + return msg + "]"; + + } + ///////////////////////////////////////////////////////////////////////// // Returns the contents of a [SelectorList] that matches only // elements that are matched by both [complex1] and [complex2]. // If no such list can be produced, returns `null`. - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // ToDo: fine-tune API to avoid unnecessary wrapper allocations - // ########################################################################## - sass::vector> unifyComplex( - const sass::vector>& complexes) + ///////////////////////////////////////////////////////////////////////// + sass::vector _unifyComplex( + sass::vector complexes, + const SourceSpan& pstate) { - SASS_ASSERT(!complexes.empty(), "Can't unify empty list"); - if (complexes.size() == 1) return complexes; + for (auto qwe : complexes) { + //std::cerr << "- IN " << qwe->inspect() << "\n"; + } + + sass::vector unifiedBase; + SelectorCombinatorObj leadingCombinator; + SelectorCombinatorObj trailingCombinator; - CompoundSelectorObj unifiedBase = SASS_MEMORY_NEW(CompoundSelector, SourceSpan("[unify]")); - for (auto complex : complexes) { - SelectorComponentObj base = complex.back(); - if (CompoundSelector * comp = base->getCompound()) { - if (unifiedBase->empty()) { - unifiedBase->concat(comp); + for (const auto& complex : complexes) { + if (complex->isUseless()) return {}; + if (complex->elements().size() == 1) { + if (complex->hasOneLeadingCombinators()) { + const SelectorCombinatorObj& lead + = complex->getLeadingCombinator(); + if (leadingCombinator.isNull()) { + //std::cerr << "===== has leading " << lead->toString() << "\n"; + leadingCombinator = lead; + } + else if (!ObjEqualityFn(leadingCombinator, lead)) { + // else if (leadingCombinator != lead) { + return {}; // Return empty list + } } - else { - for (SimpleSelectorObj simple : comp->elements()) { - unifiedBase = simple->unifyWith(unifiedBase); - if (unifiedBase.isNull()) return {}; + } + else { + // std::cerr << "Edge case detected, check\n"; + } + + if (complex->size() == 0) continue; + // Get last compound of current complex selector + // This is the one that will connect to next lead + auto base = complex->last(); + + //std::cerr << " base [" << base->inspect() << "]\n"; + + if (base->combinators().size() == 1) { + const auto& trail = base->combinators().back(); + if (trailingCombinator != nullptr) { + if (!ObjEqualityFn(trailingCombinator, trail)) { + return {}; // Return empty list } } + trailingCombinator = trail; + } + + /*if (trailingCombinator) + std::cerr << " trail [" << trailingCombinator->toString() << "]\n"; + else std::cerr << " trail [N/A]\n";*/ + + + if (unifiedBase.empty()) { + unifiedBase = base->selector()->elements(); } else { - return {}; + for (auto simple : base->selector()->elements()) { + //std::cerr << "Unify lhs : " << simple->inspect() << "\n"; + //std::cerr << "Unify rhs : " << unifiedBase[0]->inspect() << "\n"; + unifiedBase = simple->unify(unifiedBase); + if (unifiedBase.empty()) return {}; + } + } } - sass::vector> complexesWithoutBases; + // unifiedBase is nullptr, abort? + + for (auto qwe : unifiedBase) { + // std::cerr << "- base " << qwe->inspect() << "\n"; + } + + for (auto qwe : complexes) { + // std::cerr << "- CPLX " << qwe->inspect() << "\n"; + } + + sass::vector withoutBases; for (size_t i = 0; i < complexes.size(); i += 1) { - sass::vector sel = complexes[i]; - sel.pop_back(); // remove last item (base) from the list - complexesWithoutBases.push_back(std::move(sel)); + if (complexes[i]->size() < 2) continue; + ComplexSelector* unbase = SASS_MEMORY_NEW + (ComplexSelector, complexes[i].ptr()); // copy + unbase->elements().pop_back(); // remove last + withoutBases.push_back(unbase); // add unbase + } + + //std::cerr << "- NOBASE " << VecToString2(withoutBases) << "\n"; + + CompoundSelector* compound = SASS_MEMORY_NEW( + CompoundSelector, pstate, std::move(unifiedBase)); + + + sass::vector trailing; + if (trailingCombinator != nullptr) + trailing.push_back(trailingCombinator); + CplxSelComponent* component = SASS_MEMORY_NEW( + CplxSelComponent, pstate, std::move(trailing), compound); + ComplexSelector* base; + if (!leadingCombinator) base = SASS_MEMORY_NEW(ComplexSelector, pstate, {}, { component }); + else base = SASS_MEMORY_NEW(ComplexSelector, pstate, { leadingCombinator }, { component }); + + sass::vector weaving; + + + if (withoutBases.empty()) { + weaving.push_back(base); + } + else { + weaving.insert(weaving.end(), + withoutBases.begin(), + withoutBases.end() - 1); + weaving.push_back(withoutBases.back()->concatenate(base, pstate, false)); + } + // lineBreak: complexes.any((complex) => complex.lineBreak)); + + for (auto qwe : weaving) { + //std::cerr << "- WEAVE " << qwe->inspect() << "\n"; } - complexesWithoutBases.back().push_back(unifiedBase); + auto rv = weave(weaving, false); // TODO - return weave(complexesWithoutBases); + for (auto qwe : rv) { + //std::cerr << "+ WEAVED " << qwe->inspect() << "\n"; + } + + return rv; } // 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(); - } + ///////////////////////////////////////////////////////////////////////// +// CompoundSelector* CompoundSelector::unifyWith(sass::vector 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 + ///////////////////////////////////////////////////////////////////////// + // Returns the components of a [CompoundSelector] that matches only elements // matched by both this and [compound]. By default, this just returns a copy // of [compound] with this selector added to the end, or returns the original // array if this selector already exists in it. Returns `null` if unification // is impossible—for example, if there are multiple ID selectors. - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // This is implemented in `selector/simple.dart` as `SimpleSelector::unify` - // ########################################################################## - CompoundSelector* SimpleSelector::unifyWith(CompoundSelector* rhs) + ///////////////////////////////////////////////////////////////////////// + sass::vector SimpleSelector::unify( + const sass::vector& others) { - if (rhs->length() == 1) { - if (rhs->get(0)->is_universal()) { - CompoundSelector* this_compound = SASS_MEMORY_NEW(CompoundSelector, pstate()); - this_compound->append(SASS_MEMORY_COPY(this)); - CompoundSelector* unified = rhs->get(0)->unifyWith(this_compound); - if (unified == nullptr || unified != this_compound) delete this_compound; - return unified; + if (name_ == "host" || name_ == "host-context") { + for (const auto& simple : others) { // every + auto pseudo = simple->isaPseudoSelector(); + if (pseudo == nullptr) return {}; // abort + if (pseudo->isHost()) continue; + if (pseudo->selector()) continue; + return {}; // abort } } - for (const SimpleSelectorObj& sel : rhs->elements()) { - if (*this == *sel) { - return rhs; + + // Unify the simple case + else if (others.size() == 1) { + if (others[0]->isUniversal()) { + return others[0]->unify({ this }); } + else if (const auto* pseudo = others[0]->isaPseudoSelector()) { + if (pseudo->isHost() || pseudo->isHostContext()) + return others[0]->unify({ this }); + } + } + // Check if we are already part of other compound selectors + for (auto& qwe : others) { + if (PtrObjEqualityFn(qwe.ptr(), + (SimpleSelector*)this)) + return others; } - CompoundSelectorObj result = SASS_MEMORY_NEW(CompoundSelector, rhs->pstate()); + //if (std::find(others.begin(), others.end(), this) != others.end()) { + // return others; // simply return what we already have + //} + sass::vector results; + // results.reserve(rhs->size() + 1); bool addedThis = false; - for (auto simple : rhs->elements()) { + for (auto simple : others) { // Make sure pseudo selectors always come last. - if (!addedThis && simple->getPseudoSelector()) { - result->append(this); + if (!addedThis && simple->isaPseudoSelector()) { + results.push_back(this); addedThis = true; } - result->append(simple); + results.push_back(simple); } - if (!addedThis) { - result->append(this); + results.push_back(this); } - return result.detach(); - + return results; } // EO SimpleSelector::unifyWith(CompoundSelector*) - // ########################################################################## - // This is implemented in `selector/type.dart` as `PseudoSelector::unify` - // ########################################################################## - CompoundSelector* TypeSelector::unifyWith(CompoundSelector* rhs) - { - if (rhs->empty()) { - rhs->append(this); - return rhs; - } - TypeSelector* type = Cast(rhs->at(0)); - 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* IDSelector::unifyWith(CompoundSelector* rhs) + ///////////////////////////////////////////////////////////////////////// + sass::vector IDSelector::unify( + const sass::vector& rhs) { - for (const SimpleSelector* sel : rhs->elements()) { - if (const IDSelector* id_sel = Cast(sel)) { - if (id_sel->name() != name()) return nullptr; + for (const SimpleSelector* sel : rhs) { + if (const IDSelector* id_sel = sel->isaIDSelector()) { + if (id_sel->name() != name()) return {}; } } - return SimpleSelector::unifyWith(rhs); + // Dispatch to base implementation + return SimpleSelector::unify(rhs); } - // ########################################################################## + + + ///////////////////////////////////////////////////////////////////////// // This is implemented in `selector/pseudo.dart` as `PseudoSelector::unify` - // ########################################################################## - CompoundSelector* PseudoSelector::unifyWith(CompoundSelector* compound) + ///////////////////////////////////////////////////////////////////////// + sass::vector PseudoSelector::unify( + const sass::vector& compound) { - - if (compound->length() == 1 && compound->first()->is_universal()) { - // std::cerr << "implement universal pseudo\n"; + if (name_ == "host" || name_ == "host-context") { + for (const auto& simple : compound) { // every + auto pseudo = simple->isaPseudoSelector(); + if (pseudo == nullptr) return {}; // abort + if (pseudo->isHost()) continue; + if (pseudo->selector()) continue; + return {}; // abort + } } - - for (const SimpleSelectorObj& sel : compound->elements()) { - if (*this == *sel) { - return compound; + else if (compound.size() == 1) { + if (compound[0]->isUniversal()) { + return compound[0]->unify({ this }); + } + else if (const auto pseudo = compound[0]->isaPseudoSelector()) { + if (pseudo->isHost() || pseudo->isHostContext()) + return compound[0]->unify({ this }); } } - CompoundSelectorObj result = SASS_MEMORY_NEW(CompoundSelector, compound->pstate()); + // std::cerr << "CHECK " << this->inspect() << "\n"; + // std::cerr << " VS " << compound[0]->inspect() << "\n"; + + // Check if we are already part of other compound selectors + for (auto& qwe : compound) { + if (PtrObjEqualityFn(qwe.ptr(), + (SimpleSelector*)this)) + return compound; + } + // if (std::any_of(compound.begin(), compound.end(), this, PtrObjEqualityFn) != compound.end()) { + // return compound; // simply return what we already have + // } + + sass::vector results; + // results.reserve(rhs->size() + 1); bool addedThis = false; - for (auto simple : compound->elements()) { - // Make sure pseudo selectors always come last. - if (PseudoSelectorObj 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); + for (auto simple : compound) { + if (auto pseudo = simple->isaPseudoSelector()) { + if (pseudo->isPseudoElement()) { + if (isPseudoElement()) return {}; + results.push_back(this); addedThis = true; } } - result->append(simple); + results.push_back(simple); } - if (!addedThis) { - result->append(this); + results.push_back(this); } + return results; + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// - return result.detach(); + SelectorNS* SelectorNS::unity(SelectorNS* rhs) + { + sass::string ns; + const sass::string& namespace1(this->ns()); + const sass::string& namespace2(rhs->ns()); + if (nsEqual(*rhs) || rhs->isUniversalNs()) { + ns = namespace1; + } + else if (isUniversalNs()) { + ns = namespace2; + } + else { + return nullptr; + } + sass::string name; + const sass::string& name1(this->name()); + const sass::string& name2(rhs->name()); + if (name_ == rhs->name() || rhs->isUniversal()) { + name = name1; + } + else if (name1.empty() || isUniversal()) { + name = name2; + } + else { + return nullptr; + } + + auto qwe = SASS_MEMORY_NEW( + TypeSelector, pstate(), + sass::string(name), std::move(ns), + hasNs() && rhs->hasNs()); // Fixup + //std::cerr << "UnifyTypeAndEl " << qwe->inspect() << "\n"; + return qwe; } - // EO PseudoSelector::unifyWith(CompoundSelector* - // ########################################################################## + + ///////////////////////////////////////////////////////////////////////// // This is implemented in `extend/functions.dart` as `unifyUniversalAndElement` // Returns a [SimpleSelector] that matches only elements that are matched by - // both [selector1] and [selector2], which must both be either [UniversalSelector]s + // both [lhs] and [rhs], which must both be either [UniversalSelector]s // or [TypeSelector]s. If no such selector can be produced, returns `null`. // Note: libsass handles universal selector directly within the type selector - // ########################################################################## - SimpleSelector* TypeSelector::unifyWith(const SimpleSelector* rhs) + ///////////////////////////////////////////////////////////////////////// + //SimpleSelector* TypeSelector::unifyWith(const SimpleSelector* rhs2) + //{ + // if (auto rhs = rhs2->isaNameSpaceSelector()) { + // bool rhs_ns = false; + // if (!(nsEqual(*rhs) || rhs->isUniversalNs())) { + // if (!isUniversalNs()) { + // return nullptr; + // } + // rhs_ns = true; + // } + // bool rhs_name = false; + // if (!(name_ == rhs->name() || rhs->isUniversal())) { + // if (!(isUniversal())) { + // return nullptr; + // } + // rhs_name = true; + // } + // if (rhs_ns) { + // ns(rhs->ns()); + // hasNs(rhs->hasNs()); + // } + // if (rhs_name) name(rhs->name()); + // } + // return this; + //} + // EO TypeSelector::unifyWith(const SimpleSelector*) + + sass::vector TypeSelector::unifyUniversal( + const sass::vector& compound) { - bool rhs_ns = false; - if (!(is_ns_eq(*rhs) || rhs->is_universal_ns())) { - if (!is_universal_ns()) { - return nullptr; - } - rhs_ns = true; + if (compound.size() == 0) { + return { this }; } - bool rhs_name = false; - if (!(name_ == rhs->name() || rhs->is_universal())) { - if (!(is_universal())) { - return nullptr; + else if (compound.size() > 0) { + if (auto type = compound[0]->isaTypeSelector()) { + auto unified = SelectorNS::unity(type); + if (unified == nullptr) return {}; + sass::vector rv; + rv.push_back(unified); + rv.insert(rv.end(), + compound.begin() + 1, + compound.end()); + return rv; + } + + else if (auto pseudo = compound[0]->isaPseudoSelector()) { + if (pseudo->isHost() || pseudo->isHostContext()) return {}; + } + else { + // std::cerr << "hasNs: " << hasNs_ << ", ns " << ns_ << "\n"; + if (!hasNs_ || ns_ == "*") { + return compound; + } + else { + sass::vector rv; + rv.push_back(this); + rv.insert(rv.end(), + compound.begin(), + compound.end()); + return rv; + } } - rhs_name = true; } - if (rhs_ns) { - ns(rhs->ns()); - has_ns(rhs->has_ns()); + return compound; + } + + ///////////////////////////////////////////////////////////////////////// + // This is implemented in `selector/type.dart` as `PseudoSelector::unify` + ///////////////////////////////////////////////////////////////////////// + sass::vector TypeSelector::unify( + const sass::vector& compound) + { + + if (compound.empty()) return {}; + if (isUniversal()) { + return unifyUniversal(compound); + } + auto first = compound.front(); + if (first->isUniversal()) { + return first->unify({ this }); + } + else if (const auto& type = first->isaTypeSelector()) { + auto unified = SelectorNS::unity(type); + if (unified == nullptr) return {}; // abort here + if (unified->empty()) return {}; // abort here + sass::vector result; + result.push_back(unified); // prepend result + result.insert(result.end(), + compound.begin() + 1, + compound.end()); + return result; + } + else { + sass::vector result; + result.push_back(this); // prepend myself + result.insert(result.end(), + compound.begin(), + compound.end()); + return result; } - if (rhs_name) name(rhs->name()); - return this; } - // EO TypeSelector::unifyWith(const SimpleSelector*) - // ########################################################################## + + ///////////////////////////////////////////////////////////////////////// // Unify two complex selectors. Internally calls `unifyComplex` // and then wraps the result in newly create ComplexSelectors. - // ########################################################################## - SelectorList* ComplexSelector::unifyWith(ComplexSelector* rhs) + ///////////////////////////////////////////////////////////////////////// + SelectorList* ComplexSelector::unifyList(ComplexSelector* rhs) { - SelectorListObj list = SASS_MEMORY_NEW(SelectorList, pstate()); - sass::vector> rv = - unifyComplex({ elements(), rhs->elements() }); - for (sass::vector items : rv) { - ComplexSelectorObj sel = SASS_MEMORY_NEW(ComplexSelector, pstate()); - sel->elements() = std::move(items); - list->append(sel); - } - return list.detach(); + sass::vector rv = + _unifyComplex({ this, rhs }, pstate()); + sass::vector list; + //// list.reserve(rv.size()); + if (rv.empty()) return nullptr; + for (ComplexSelectorObj& items : rv) { + list.push_back(items); + } + SelectorListObj qwe = SASS_MEMORY_NEW(SelectorList, + pstate(), std::move(list)); + // debug_ast(qwe); + return qwe.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()); + sass::vector selectors; // 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())); + for (const ComplexSelectorObj& seq1 : elements()) { + for (const ComplexSelectorObj& seq2 : rhs->elements()) { + if (SelectorList* unified = seq1->unifyList(seq2)) { + selectors.insert(selectors.end(), + unified->begin(), unified->end()); } } } - return slist; + return SASS_MEMORY_NEW(SelectorList, + pstate(), std::move(selectors)); } // EO SelectorList::unifyWith(SelectorList*) - // ########################################################################## - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// } diff --git a/src/ast_sel_weave.cpp b/src/ast_sel_weave.cpp index 46b83861bf..5020825d02 100644 --- a/src/ast_sel_weave.cpp +++ b/src/ast_sel_weave.cpp @@ -1,62 +1,82 @@ -// sass.hpp must go before all system headers to get the -// __EXTENSIONS__ fix on Solaris. -#include "sass.hpp" +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#include "ast_selectors.hpp" -#include "ast.hpp" #include "permutate.hpp" #include "dart_helpers.hpp" namespace Sass { - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // Returns whether or not [compound] contains a `::root` selector. - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// bool hasRoot(const CompoundSelector* compound) { - // Libsass does not yet know the root selector + for (const SimpleSelector* simple : compound->elements()) { + if (const PseudoSelector* pseudo = simple->isaPseudoSelector()) { + if (pseudo->isClass() && pseudo->normalized() == "root") { + return true; + } + } + } return false; } // EO hasRoot - // ########################################################################## + bool hasRootish(const CompoundSelector* compound) + { + for (const SimpleSelector* simple : compound->elements()) { + if (const PseudoSelector* pseudo = simple->isaPseudoSelector()) { + if (pseudo->isClass()) { + if (pseudo->normalized() == "root") return true; + if (pseudo->normalized() == "scope") return true; + if (pseudo->normalized() == "host") return true; + if (pseudo->normalized() == "host-context") return true; + } + } + } + return false; + } + // EO hasRoot + + ///////////////////////////////////////////////////////////////////////// // Returns whether a [CompoundSelector] may contain only // one simple selector of the same type as [simple]. - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// bool isUnique(const SimpleSelector* simple) { - if (Cast(simple)) return true; - if (const PseudoSelector * pseudo = Cast(simple)) { - if (pseudo->is_pseudo_element()) return true; + if (simple->isaIDSelector()) return true; + if (const PseudoSelector* pseudo = simple->isaPseudoSelector()) { + if (pseudo->isPseudoElement()) return true; } return false; } // EO isUnique - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // Returns whether [complex1] and [complex2] need to be unified to // produce a valid combined selector. This is necessary when both // selectors contain the same unique simple selector, such as an ID. - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// bool mustUnify( - const sass::vector& complex1, - const sass::vector& complex2) + const CplxSelComponentVector& complex1, + const CplxSelComponentVector& complex2) { sass::vector uniqueSelectors1; - for (const SelectorComponent* component : complex1) { - if (const CompoundSelector * compound = component->getCompound()) { + for (const CplxSelComponent* component : complex1) { + if (const CompoundSelector* compound = component->selector()) { for (const SimpleSelector* sel : compound->elements()) { if (isUnique(sel)) { - uniqueSelectors1.push_back(sel); + uniqueSelectors1.emplace_back(sel); } } } } if (uniqueSelectors1.empty()) return false; - - // ToDo: unsure if this is correct - for (const SelectorComponent* component : complex2) { - if (const CompoundSelector * compound = component->getCompound()) { + for (const CplxSelComponent* component : complex2) { + if (const CompoundSelector* compound = component->selector()) { for (const SimpleSelector* sel : compound->elements()) { if (isUnique(sel)) { for (auto check : uniqueSelectors1) { @@ -68,96 +88,115 @@ namespace Sass { } return false; - } // EO isUnique - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // Helper function used by `weaveParents` - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// bool cmpGroups( - const sass::vector& group1, - const sass::vector& group2, - sass::vector& select) + const CplxSelComponentVector& group1, + const CplxSelComponentVector& group2, + CplxSelComponentVector& select) { - if (group1.size() == group2.size() && std::equal(group1.begin(), group1.end(), group2.begin(), PtrObjEqualityFn)) { + if (ListEquality(group1, group2, PtrObjEqualityFn)) + { + // std::cerr << ("List is equal\n"); select = group1; return true; } - if (!Cast(group1.front())) { + // std::cerr << "cmp1: " << InspectVector(group1) << "\n"; + // std::cerr << "cmp2: " << InspectVector(group2) << "\n"; + + if (!group1.front()->selector()) { + // std::cerr << "group1 front has no selector\n"; select = {}; return false; } - if (!Cast(group2.front())) { + if (!group2.front()->selector()) { + // std::cerr << "group2 front has no selector\n"; select = {}; return false; } if (complexIsParentSuperselector(group1, group2)) { + // std::cerr << ("!Complex is parent super 1\n"); select = group2; return true; } if (complexIsParentSuperselector(group2, group1)) { + // std::cerr << ("!Complex is parent super 2\n"); select = group1; return true; } if (!mustUnify(group1, group2)) { - select = {}; + // std::cerr << ("!Must not unify\n"); + select.clear(); return false; } - sass::vector> unified - = unifyComplex({ group1, group2 }); - if (unified.empty()) return false; - if (unified.size() > 1) return false; - select = unified.front(); + auto span = SourceSpan::internal("[BASE]"); + CplxSelComponentVector comp1(group1); + CplxSelComponentVector comp2(group2); + auto q1 = SASS_MEMORY_NEW(ComplexSelector, span, std::move(comp1)); + auto q2 = SASS_MEMORY_NEW(ComplexSelector, span, std::move(comp2)); + auto unified = _unifyComplex({ q2, q1 }, span); + for (auto q : unified) { + // std::cerr << "_weaveParents => " << q->toString() << "\n"; + } + if (unified.size() == 1) { + select = unified[0]->elements(); + } + else { + return false; + } return true; } // EO cmpGroups - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // Helper function used by `weaveParents` - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// template bool checkForEmptyChild(const T& item) { return item.empty(); } // EO checkForEmptyChild - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // Helper function used by `weaveParents` - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// bool cmpChunkForEmptySequence( - const sass::vector>& seq, - const sass::vector& group) + const sass::vector& seq, + const CplxSelComponentVector& group) { return seq.empty(); } // EO cmpChunkForEmptySequence - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // Helper function used by `weaveParents` - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// bool cmpChunkForParentSuperselector( - const sass::vector>& seq, - const sass::vector& group) + const sass::vector& seq, + const CplxSelComponentVector& 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)`. - // ########################################################################## + // EO cmpChunkForParentSuperselector + + ///////////////////////////////////////////////////////////////////////// + // Returns all orderings of initial subsequences of [queue1] and [queue2]. + // The [done] callback is used to determine the extent of the initial + // subsequences. It's called with each queue until it returns `true`. + // Destructively removes the initial subsequences of [queue1] and [queue2]. + // For example, given `(A B C | D E)` and `(1 2 | 3 4 5)` (with `|` denoting + // the boundary of the initial subsequence), this would return `[(A B C 1 2), + // (1 2 A B C)]`. The queues would then contain `(D E)` and `(3 4 5)`. + ///////////////////////////////////////////////////////////////////////// template sass::vector> getChunks( sass::vector& queue1, sass::vector& queue2, @@ -166,112 +205,128 @@ namespace Sass { sass::vector chunk1; while (!done(queue1, group)) { - chunk1.push_back(queue1.front()); + chunk1.emplace_back(queue1.front()); queue1.erase(queue1.begin()); } sass::vector chunk2; while (!done(queue2, group)) { - chunk2.push_back(queue2.front()); + chunk2.emplace_back(queue2.front()); queue2.erase(queue2.begin()); } if (chunk1.empty() && chunk2.empty()) return {}; - else if (chunk1.empty()) return { chunk2 }; - else if (chunk2.empty()) return { chunk1 }; - - sass::vector choice1(chunk1), choice2(chunk2); - std::move(std::begin(chunk2), std::end(chunk2), - std::inserter(choice1, std::end(choice1))); - std::move(std::begin(chunk1), std::end(chunk1), - std::inserter(choice2, std::end(choice2))); - return { choice1, choice2 }; + else if (chunk1.empty()) { + return { chunk2 }; + } + else if (chunk2.empty()) { + return { chunk1 }; + } + + sass::vector> result; + result.emplace_back(chunk1); + result.emplace_back(chunk2); + result.front().insert(result.front().end(), + std::make_move_iterator(chunk2.begin()), + std::make_move_iterator(chunk2.end())); + result.back().insert(result.back().end(), + std::make_move_iterator(chunk1.begin()), + std::make_move_iterator(chunk1.end())); + return result; } // EO getChunks - // ########################################################################## - // If the first element of [queue] has a `::root` + ///////////////////////////////////////////////////////////////////////// + // If the first element of [queue] has a `::root` // selector, removes and returns that element. - // ########################################################################## - CompoundSelectorObj getFirstIfRoot(sass::vector& queue) { + ///////////////////////////////////////////////////////////////////////// + CplxSelComponentObj getFirstIfRoot(CplxSelComponentVector& queue) { if (queue.empty()) return {}; - SelectorComponent* first = queue.front(); - if (CompoundSelector* sel = Cast(first)) { + CplxSelComponent* first = queue.front(); + if (CompoundSelector* sel = first->selector()) { if (!hasRoot(sel)) return {}; queue.erase(queue.begin()); - return sel; + return first; + } + return {}; + } + // EO getFirstIfRoot + + CplxSelComponentObj _firstIfRootish(CplxSelComponentVector& queue) { + if (queue.empty()) return {}; + CplxSelComponent* first = queue.front(); + if (CompoundSelector* sel = first->selector()) { + + if (!hasRootish(sel)) return {}; + queue.erase(queue.begin()); + return first; + } 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)]`. - // ########################################################################## - sass::vector> groupSelectors( - const sass::vector& components) + ///////////////////////////////////////////////////////////////////////// + sass::vector groupSelectors( + const CplxSelComponentVector& components) { - bool lastWasCompound = false; - sass::vector group; - sass::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; + sass::vector groups; + CplxSelComponentVector group; + for (auto component : components) { + group.push_back(component); + if (component->combinators().empty()) { + groups.emplace_back(std::move(group)); + group.clear(); // needed after move? } } if (!group.empty()) { - groups.push_back(group); + groups.emplace_back(group); } return groups; } // EO groupSelectors - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // Extracts leading [Combinator]s from [components1] and [components2] // and merges them together into a single list of combinators. // If there are no combinators to be merged, returns an empty list. // If the combinators can't be merged, returns `null`. - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// bool mergeInitialCombinators( - sass::vector& components1, - sass::vector& components2, - sass::vector& result) + CplxSelComponentVector& components1, + CplxSelComponentVector& components2, + CplxSelComponentVector& result) { - sass::vector combinators1; - while (!components1.empty() && Cast(components1.front())) { - SelectorCombinatorObj front = Cast(components1.front()); + CplxSelComponentVector combinators1; + while (!components1.empty() && components1.front()->selector()) { + // SelectorCombinator* front = components1.front()->isaSelectorCombinator(); components1.erase(components1.begin()); - combinators1.push_back(front); + // combinators1.emplace_back(front); } - sass::vector combinators2; - while (!components2.empty() && Cast(components2.front())) { - SelectorCombinatorObj front = Cast(components2.front()); + CplxSelComponentVector combinators2; + while (!components2.empty() && components2.front()->selector()) { + // SelectorCombinator* front = components2.front()->isaSelectorCombinator(); components2.erase(components2.begin()); - combinators2.push_back(front); + // combinators2.emplace_back(front); } // If neither sequence of combinators is a subsequence // of the other, they cannot be merged successfully. - sass::vector LCS = lcs(combinators1, combinators2); + CplxSelComponentVector LCS = lcs(combinators1, combinators2); - if (ListEquality(LCS, combinators1, PtrObjEqualityFn)) { + if (ListEquality(LCS, combinators1, PtrObjEqualityFn)) { result = combinators2; return true; } - if (ListEquality(LCS, combinators2, PtrObjEqualityFn)) { + if (ListEquality(LCS, combinators2, PtrObjEqualityFn)) { result = combinators1; return true; } @@ -281,50 +336,450 @@ namespace Sass { } // EO mergeInitialCombinators - // ########################################################################## + + ///////////////////////////////////////////////////////////////////////// + // 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]]`. + ///////////////////////////////////////////////////////////////////////// + + /// 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]`. + /// + /// The [span] will be used for any new combined selectors. + /// + /// If [forceLineBreak] is `true`, this will mark all returned complex selectors + /// as having line breaks. + ComplexSelector* ComplexSelector::withAdditionalComponent( + CplxSelComponent* component, SourceSpan& span, + bool forceLineBreak = false) + { + SelectorCombinatorVector combo(leadingCombinators_); + CplxSelComponentVector comps(elements_); + comps.push_back(component); + return SASS_MEMORY_NEW(ComplexSelector, span, + std::move(combo), std::move(comps), + hasLineBreak_ || forceLineBreak); + } + + sass::vector weave( + const sass::vector& complexes, + bool forceLineBreak) + { + + + /* + + + sass::vector prefixes; + + prefixes.emplace_back(complexes.at(0)); + + for (size_t i = 1; i < complexes.size(); i += 1) { + + if (complexes[i]->empty()) { + continue; + } + const ComplexSelectorObj& complex = complexes[i]; + CplxSelComponent* target = complex->elements().back(); + if (complex->size() == 1) { + for (auto& prefix : prefixes) { + prefix->elements().push_back(target); + } + continue; + } + + ComplexSelectorObj parents = SASS_MEMORY_COPY(complex); + + parents->elements().pop_back(); + + sass::vector newPrefixes; + for (ComplexSelectorObj prefix : prefixes) { + sass::vector + parentPrefixes = weaveParents(prefix, parents); + if (parentPrefixes.empty()) continue; + for (auto& parentPrefix : parentPrefixes) { + parentPrefix->elements().emplace_back(target); + newPrefixes.push_back(parentPrefix); + } + } + prefixes = newPrefixes; + + } + return prefixes; + + */ + + if (complexes.empty()) return complexes; + + for (auto qwe : complexes) { + for (auto qwe2 : qwe->elements()) { + // std::cerr << "weaving [" << qwe2->inspecter() << "]\n"; + } + } + + if (complexes.size() == 1) { + auto complex = complexes.front(); + // Force line breaks if required + return complexes; + } + + sass::vector prefixes; + prefixes.emplace_back(complexes.front()); + + for (size_t i = 1; i < complexes.size(); i += 1) { + const ComplexSelectorObj& complex = complexes[i]; + if (complex->elements().size() == 1) { + for (auto& prefix : prefixes) { + prefix = prefix->concatenate(complex, complex->pstate(), forceLineBreak); + // prefix->elements().push_back(complex); + // prefix->concatenate(complex); + } + continue; + } + + for (auto asd : prefixes) { + // std::cerr << "weaver : " << asd->inspect() << "\n"; + } + + // CplxSelComponentVector parents(complex); + // parents.pop_back(); + + sass::vector newPrefixes; + for (const ComplexSelectorObj& prefix : prefixes) { + sass::vector weaveds + = weaveParents(prefix, complex); + if (weaveds.empty()) continue; + for (const auto& parent : weaveds) { + SourceSpan span(complex->pstate()); + auto asd = parent->withAdditionalComponent( + complex->elements().back(), + span, forceLineBreak); + newPrefixes.push_back(asd); + + // // Still returns multiple parents here + // prefix->elements().insert(prefix->end(), + // std::make_move_iterator(parents.begin()), + // std::make_move_iterator(parents.end())); + } + } + prefixes = newPrefixes; + + } + return prefixes; + + } + // EO weave + + + bool _mergeLeadingCombinators( + const SelectorCombinatorVector& combinators1, + const SelectorCombinatorVector& combinators2, + SelectorCombinatorVector& result) + { + if (combinators1.empty()) { + result = combinators2; + } + else if (combinators2.empty()) { + result = combinators1; + } + else if (combinators1.size() > 1) { + // Nothing to add in this case? + } + else if (combinators2.size() > 1) { + // Nothing to add in this case? + } + else if (ListEquality(combinators1, combinators2, PtrObjEqualityFn)) { + result = combinators1; + } + return true; + } + // // Allow null arguments just to make calls to `Iterable.reduce()` easier. + // switch ((combinators1, combinators2)) { + // (null, _) || (_, null) = > null, + // (List(length: > 1), _) || (_, List(length: > 1)) = > null, + // ([], var combinators) || (var combinators, []) = > combinators, + // _ = > listEquals(combinators1, combinators2) ? combinators1 : null + // }; + + + template + T backOrNull(sass::vector asd) { + if (asd.size() == 0) return nullptr; + return asd.back(); + } + + + ///////////////////////////////////////////////////////////////////////// + // 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. + ///////////////////////////////////////////////////////////////////////// + CompoundSelector* unifyCompound( + CompoundSelector* compound1, + CompoundSelector* compound2) + { + auto result = compound2->elements(); + for (auto simple : compound1->elements()) { + // std::cerr << "==== unifyCompound [" << simple->inspect() << "][" << compound2->inspect() << "]\n"; + auto unified = simple->unify(result); + if (unified.empty()) return nullptr; + // for (auto& foo : unified) { std::cerr << " ==> " << foo->inspect() << "\n"; } + result = unified; + } + return SASS_MEMORY_NEW(CompoundSelector, + compound1->pstate(), std::move(result)); + } + + + bool _mergeTrailingCombinators(const SourceSpan& span, + CplxSelComponentVector& components1, CplxSelComponentVector& components2, + sass::vector>& result) + { + + // std::cerr << "process trailing\n"; + + // for (const auto& c1 : components1) { if (c1) std::cerr << "merge trails in1 " << c1->inspecter() << "\n"; } + // for (const auto& c2 : components2) { if (c2) std::cerr << "merge trails in2 " << c2->inspecter() << "\n"; } + + SelectorCombinatorVector combinators1, combinators2; + if (components1.size() > 0) combinators1 = components1.back()->combinators(); + if (components2.size() > 0) combinators2 = components2.back()->combinators(); + + if (combinators1.empty() && combinators2.empty()) return true; + if (combinators1.size() > 1 || combinators2.size() > 1) return false; + + auto first1 = combinators1.empty() ? nullptr : combinators1.front(); + auto first2 = combinators2.empty() ? nullptr : combinators2.front(); + + // if (first1 == nullptr) std::cerr << " first1 null\n"; + // else std::cerr << " first1 " << first1->toString() << "\n"; + // if (first2 == nullptr) std::cerr << " first2 null\n"; + // else std::cerr << " first2 " << first2->toString() << "\n"; + + if (first1 != nullptr && first2 != nullptr) + { + if (first1->isFollowingSibling() && first2->isFollowingSibling()) { + auto component1 = components1.back(); + auto component2 = components2.back(); + components1.pop_back(); // consumed + components2.pop_back(); // consumed + if (component1->selector()->isSuperselectorOf(component2->selector())) { + result.push_back({ { component2 } }); + } + else if (component2->selector()->isSuperselectorOf(component1->selector())) { + result.push_back({ { component1 } }); + } + else { + sass::vector choices; + choices.push_back({ component1, component2 }); + choices.push_back({ component2, component1 }); + if (CompoundSelectorObj unified = unifyCompound( + component1->selector(),component2->selector())) { + choices.push_back({ SASS_MEMORY_NEW(CplxSelComponent, + span, { combinators1.front() }, unified) }); + } + result.push_back(choices); + } + // std::cerr << "Merge case 1\n"; + } + else if (first1->isFollowingSibling() && first2->isNextSibling()) { + auto next = components2.back(); + auto following = components1.back(); + components1.pop_back(); // consumed + components2.pop_back(); // consumed + + // std::cerr << "next1 " << next->inspecter() << "\n"; + // std::cerr << "following1 " << following->inspecter() << "\n"; + + if (following->selector()->isSuperselectorOf(next->selector())) { + result.push_back({ { next } }); + } + else if (auto unified = unifyCompound(following->selector(), next->selector())) + { + SelectorCombinatorVector asd = next->combinators(); + result.push_back({ + {following, next}, + { new CplxSelComponent(span, std::move(asd), unified)} + }); + } + else { + result.push_back({ + {following, next} + }); + } + // std::cerr << "Merge case 2a\n"; + } + else if (first1->isNextSibling() && first2->isFollowingSibling()) { + auto next = components1.back(); + auto following = components2.back(); + components1.pop_back(); // consumed + components2.pop_back(); // consumed + + // std::cerr << "next2 " << next->inspecter() << "\n"; + // std::cerr << "following2 " << following->inspecter() << "\n"; + + if (following->selector()->isSuperselectorOf(next->selector())) { + result.push_back({ { next } }); + } + else if (auto unified = unifyCompound(following->selector(), next->selector())) + { + SelectorCombinatorVector asd = next->combinators(); + result.push_back({ + {following, next}, + { new CplxSelComponent(span, std::move(asd), unified)} + }); + } + else { + result.push_back({ + {following, next} + }); + } + // std::cerr << "Merge case 2b\n"; + } + else if (first1->isChild() && !first2->isChild()) { + result.push_back({ { components2.back() } }); + components2.pop_back(); // has been consumed + // std::cerr << "Merge case 3\n"; + } + else if (!first1->isChild() && first2->isChild()) { + result.push_back({ { components1.back() } }); + components1.pop_back(); // has been consumed + // std::cerr << "Merge case 3\n"; + } + else if (first1->combinator() == first2->combinator()) { + + auto lst1 = components1.back(); + components1.pop_back(); + + auto lst2 = components2.back(); + components2.pop_back(); + + auto unified = unifyCompound( + lst1->selector(), lst2->selector()); + if (unified == nullptr) return false; + + // std::cerr << " cmp " << unified->inspect() << "\n"; + + result.push_back({ { SASS_MEMORY_NEW(CplxSelComponent, span, { combinators1.front() }, unified) } }); + + // std::cerr << "Merge case 4\n"; + } + else { + // std::cerr << "Merge case other\n"; + return false; + } + } + else if (first1 != nullptr) + { + auto descendantComponents = backOrNull(components2); + auto combinatorComponents = backOrNull(components1); + + //std::cerr << "descendantComponents " << descendantComponents->inspecter() << "\n"; + //std::cerr << "combinatorComponents " << combinatorComponents->inspecter() << "\n"; + + if (first1->isChild()) { + if (descendantComponents > 0 && descendantComponents->selector()->isSuperselectorOf(combinatorComponents->selector())) { + components2.pop_back(); + } + } + result.push_back({ { components1.back() } }); + components1.pop_back(); + // std::cerr << "Merge case 5a\n"; + } + else if (first2 != nullptr) + { + auto descendantComponents = backOrNull(components1); + auto combinatorComponents = backOrNull(components2); + if (first2->isChild()) { + if (descendantComponents > 0 && descendantComponents->selector()->isSuperselectorOf(combinatorComponents->selector())) { + components1.pop_back(); + } + } + result.push_back({ { components2.back() } }); + components2.pop_back(); + // std::cerr << "Merge case 5b\n"; + } + else { + return false; + } + + for (auto f1 : components1) { + // std::cerr << "final trail merge 1 " << f1->inspecter() << "\n"; + } + for (auto f2 : components2) { + // std::cerr << "final trail merge 2 " << f2->inspecter() << "\n"; + } + + return _mergeTrailingCombinators(span, components1, components2, result);; + + } + + + + ///////////////////////////////////////////////////////////////////////// // Extracts trailing [Combinator]s, and the selectors to which they apply, // from [components1] and [components2] and merges them together into a // single list. If there are no combinators to be merged, returns an // empty list. If the sequences can't be merged, returns `null`. - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// bool mergeFinalCombinators( - sass::vector& components1, - sass::vector& components2, - sass::vector>>& result) + CplxSelComponentVector& components1, + CplxSelComponentVector& components2, + sass::vector>& result) { - if (components1.empty() || !Cast(components1.back())) { - if (components2.empty() || !Cast(components2.back())) { + if (components1.empty() || components1.back()->combinators().empty()) { + if (components2.empty() || components2.back()->combinators().empty()) { return true; } } - - sass::vector combinators1; - while (!components1.empty() && Cast(components1.back())) { - SelectorCombinatorObj back = Cast(components1.back()); + + CplxSelComponentVector combinators1; + while (!components1.empty() && components1.back()->combinators().size() != 0) { + //SelectorCombinatorObj back = components1.back()->combinators(); components1.erase(components1.end() - 1); - combinators1.push_back(back); + //combinators1.emplace_back(back); } - sass::vector combinators2; - while (!components2.empty() && Cast(components2.back())) { - SelectorCombinatorObj back = Cast(components2.back()); + CplxSelComponentVector combinators2; + while (!components2.empty() && components2.back()->combinators().size() != 0) { + //SelectorCombinatorObj back = components2.back()->combinators(); components2.erase(components2.end() - 1); - combinators2.push_back(back); + //combinators2.emplace_back(back); } - // reverse now as we used push_back (faster than new alloc) + // reverse now as we used emplace_back (faster than new alloc) std::reverse(combinators1.begin(), combinators1.end()); std::reverse(combinators2.begin(), combinators2.end()); if (combinators1.size() > 1 || combinators2.size() > 1) { - // If there are multiple combinators, something hacky's going on. If one - // is a supersequence of the other, use that, otherwise give up. - auto LCS = lcs(combinators1, combinators2); - if (ListEquality(LCS, combinators1, PtrObjEqualityFn)) { + // If there are multiple combinators, something strange going on. If one + // is a super-sequence of the other, use that, otherwise give up. + auto LCS = lcs(combinators1, combinators2); + if (ListEquality(LCS, combinators1, PtrObjEqualityFn)) { result.push_back({ combinators2 }); } - else if (ListEquality(LCS, combinators2, PtrObjEqualityFn)) { + else if (ListEquality(LCS, combinators2, PtrObjEqualityFn)) { result.push_back({ combinators1 }); } else { @@ -336,33 +791,33 @@ namespace Sass { // 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 (!combinators1.empty()) combinator1 = combinators1.back()->isaSelectorCombinator(); + //if (!combinators2.empty()) combinator2 = combinators2.back()->isaSelectorCombinator(); if (!combinator1.isNull() && !combinator2.isNull()) { - CompoundSelector* compound1 = Cast(components1.back()); - CompoundSelector* compound2 = Cast(components2.back()); + // CompoundSelector* compound1 = components1.back()->selector(); + // CompoundSelector* compound2 = components2.back()->selector(); components1.pop_back(); components2.pop_back(); - + /* if (combinator1->isGeneralCombinator() && combinator2->isGeneralCombinator()) { if (compound1->isSuperselectorOf(compound2)) { - result.push_back({ { compound2, combinator2 } }); + result.push_back({ { compound2, combinator2.ptr() } }); } else if (compound2->isSuperselectorOf(compound1)) { - result.push_back({ { compound1, combinator1 } }); + result.push_back({ { compound1, combinator1.ptr() } }); } else { - sass::vector> choices; - choices.push_back({ compound1, combinator1, compound2, combinator2 }); - choices.push_back({ compound2, combinator2, compound1, combinator1 }); + sass::vector choices; + choices.push_back({ compound1, combinator1.ptr(), compound2, combinator2.ptr() }); + choices.push_back({ compound2, combinator2.ptr(), compound1, combinator1.ptr() }); if (CompoundSelector* unified = compound1->unifyWith(compound2)) { - choices.push_back({ unified, combinator1 }); + choices.push_back({ unified, combinator1.ptr() }); } - result.push_back(choices); + result.emplace_back(choices); } } else if ((combinator1->isGeneralCombinator() && combinator2->isAdjacentCombinator()) || @@ -378,11 +833,11 @@ namespace Sass { } else { CompoundSelectorObj unified = compound1->unifyWith(compound2); - sass::vector> items; - + sass::vector items; + if (!unified.isNull()) { items.push_back({ - unified, nextSiblingCombinator + unified.ptr(), nextSiblingCombinator }); } @@ -393,188 +848,205 @@ namespace Sass { nextSiblingCombinator, }); - result.push_back(items); + result.emplace_back(items); } } else if (combinator1->isChildCombinator() && (combinator2->isAdjacentCombinator() || combinator2->isGeneralCombinator())) { - result.push_back({ { compound2, combinator2 } }); - components1.push_back(compound1); - components1.push_back(combinator1); + result.push_back({ { compound2, combinator2.ptr() } }); + components1.emplace_back(compound1); + components1.emplace_back(combinator1); } else if (combinator2->isChildCombinator() && (combinator1->isAdjacentCombinator() || combinator1->isGeneralCombinator())) { - result.push_back({ { compound1, combinator1 } }); - components2.push_back(compound2); - components2.push_back(combinator2); + result.push_back({ { compound1, combinator1.ptr() } }); + components2.emplace_back(compound2); + components2.emplace_back(combinator2); } else if (*combinator1 == *combinator2) { CompoundSelectorObj unified = compound1->unifyWith(compound2); if (unified.isNull()) return false; - result.push_back({ { unified, combinator1 } }); + result.push_back({ { unified.ptr(), combinator1.ptr() } }); } else { return false; } + */ return mergeFinalCombinators(components1, components2, result); } - else if (!combinator1.isNull()) { - + // else if (!combinator1.isNull()) { + /* if (combinator1->isChildCombinator() && !components2.empty()) { - const CompoundSelector* back1 = Cast(components1.back()); - const CompoundSelector* back2 = Cast(components2.back()); + const CompoundSelector* back1 = components1.back()->isaCompoundSelector(); + const CompoundSelector* back2 = components2.back()->isaCompoundSelector(); if (back1 && back2 && back2->isSuperselectorOf(back1)) { components2.pop_back(); } } - result.push_back({ { components1.back(), combinator1 } }); + result.push_back({ { components1.back(), combinator1.ptr() } }); components1.pop_back(); + */ + //return mergeFinalCombinators(components1, components2, result); - return mergeFinalCombinators(components1, components2, result); - - } - + //} + /* if (combinator2->isChildCombinator() && !components1.empty()) { - const CompoundSelector* back1 = Cast(components1.back()); - const CompoundSelector* back2 = Cast(components2.back()); + const CompoundSelector* back1 = components1.back()->isaCompoundSelector(); + const CompoundSelector* back2 = components2.back()->isaCompoundSelector(); if (back1 && back2 && back1->isSuperselectorOf(back2)) { components1.pop_back(); } } - result.push_back({ { components2.back(), combinator2 } }); + result.push_back({ { components2.back(), combinator2.ptr() } }); 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]]`. - // ########################################################################## - sass::vector> weave( - const sass::vector>& complexes) { - sass::vector> prefixes; - prefixes.push_back(complexes.at(0)); - for (size_t i = 1; i < complexes.size(); i += 1) { + sass::vector weaveParents( + ComplexSelector* prefix, ComplexSelector* base) + { - if (complexes[i].empty()) { - continue; - } - const sass::vector& complex = complexes[i]; - SelectorComponent* target = complex.back(); - if (complex.size() == 1) { - for (auto& prefix : prefixes) { - prefix.push_back(target); - } - continue; - } + //std::cerr << "wp prefix " << prefix->inspect() << "\n"; + //std::cerr << "wp base " << base->inspect() << "\n"; - sass::vector parents(complex); + SelectorCombinatorVector lead; + bool rs1 = _mergeLeadingCombinators( + prefix->leadingCombinators(), + base->leadingCombinators(), + lead); - parents.pop_back(); + // _mergeLeadingCombinators must report success or not + if (rs1 == false) return {}; - sass::vector> newPrefixes; - for (sass::vector prefix : prefixes) { - sass::vector> - parentPrefixes = weaveParents(prefix, parents); - if (parentPrefixes.empty()) continue; - for (auto& parentPrefix : parentPrefixes) { - parentPrefix.push_back(target); - newPrefixes.push_back(parentPrefix); - } - } - prefixes = newPrefixes; + // for (SelectorCombinator* asd : lead) { std::cerr << "merged lead " << asd->toString() << "\n"; } + + if (base->empty()) throw "Need base"; + + CplxSelComponentVector leads; + + CplxSelComponentVector queue1(prefix->begin(), prefix->end()); + CplxSelComponentVector queue2(base->begin(), base->end() - 1); + + for (auto g1 : queue1) { + //std::cerr << "q1: " << g1->inspecter() << "\n"; + } + for (auto g2 : queue2) { + //std::cerr << "q2: " << g2->inspecter() << "\n"; } - 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. - // ########################################################################## - sass::vector> weaveParents( - sass::vector queue1, - sass::vector queue2) - { + // sass::vector> trails; + + sass::vector> trails; + bool ok = _mergeTrailingCombinators( + base->pstate(), queue1, queue2, trails); + + if (ok == false) return {}; + + for (auto g2 : queue2) { + //std::cerr << "after q2: " << g2->inspecter() << "\n"; + } + - sass::vector leads; - sass::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()); + for (auto as : trails) { + //std::cerr << "---\n"; + for (auto bs : as) { + //std::cerr << "{{{\n"; + for (auto cs : bs) { + //std::cerr << "merged trail " << cs->toString() << "\n"; + } + //std::cerr << "}}}\n"; + } + } + + + // std::cerr << "======= OK\n"; + // if (!mergeInitialCombinators(queue1, queue2, leads)) return {}; + // if (!mergeFinalCombinators(queue1, queue2, trails)) return {}; + + // 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); + CplxSelComponentObj root1(_firstIfRootish(queue1)); + CplxSelComponentObj root2(_firstIfRootish(queue2)); if (!root1.isNull() && !root2.isNull()) { - CompoundSelectorObj root = root1->unifyWith(root2); + // CompoundSelectorObj root = root1->selector()->unifyWith(root2->selector()); + CompoundSelectorObj root = unifyCompound(root1->selector(), root2->selector()); if (root.isNull()) return {}; // null - queue1.insert(queue1.begin(), root); - queue2.insert(queue2.begin(), root); + queue1.insert(queue1.begin(), root.ptr()->wrapInComponent(root1->combinators())); + queue2.insert(queue2.begin(), root.ptr()->wrapInComponent(root2->combinators())); } else if (!root1.isNull()) { - queue2.insert(queue2.begin(), root1); + queue1.insert(queue1.begin(), root1.ptr()); + queue2.insert(queue2.begin(), root1.ptr()); } else if (!root2.isNull()) { - queue1.insert(queue1.begin(), root2); + queue1.insert(queue1.begin(), root2.ptr()); + queue2.insert(queue2.begin(), root2.ptr()); } + //for (auto g1 : queue1) { std::cerr << "q1: " << g1->inspect() << "\n"; } + //for (auto g2 : queue2) { std::cerr << "q2: " << g2->inspect() << "\n"; } + // group into sub-lists so no sub-list contains two adjacent ComplexSelectors. - sass::vector> groups1 = groupSelectors(queue1); - sass::vector> groups2 = groupSelectors(queue2); + sass::vector groups1 = groupSelectors(queue1); + sass::vector groups2 = groupSelectors(queue2); + + //for (auto g1 : groups1) { std::cerr << "g1: " << InspectVector(g1) << "\n"; } + //for (auto g2 : groups2) { std::cerr << "g2: " << InspectVector(g2) << "\n"; } // The main array to store our choices that will be permutated - sass::vector>> choices; + sass::vector> choices; // append initial combinators - choices.push_back({ leads }); +// choices.push_back({ std::move(leads) }); + + // std::cerr << "---- start lcs\n"; + + // std::reverse(groups2.begin(), groups2.end()); - sass::vector> LCS = - lcs>(groups1, groups2, cmpGroups); + sass::vector LCS = + lcs(groups1, groups2, cmpGroups); + + //std::cerr << "---- got lcs:\n"; + + for (auto g2 : LCS) { + for (auto g : g2) { + //std::cerr << "lcs: " << g->inspecter() << "\n"; + } + } + + //std::cerr << "---- EO lcs: !!!!!!!\n"; for (auto group : LCS) { // Create junks from groups1 and groups2 - sass::vector>> - chunks = getChunks>( + sass::vector> + chunks = getChunks( groups1, groups2, group, cmpChunkForParentSuperselector); // Create expanded array by flattening chunks2 inner - sass::vector> + sass::vector expanded = flattenInner(chunks); // Prepare data structures - choices.push_back(expanded); + choices.emplace_back(expanded); choices.push_back({ group }); if (!groups1.empty()) { groups1.erase(groups1.begin()); @@ -585,32 +1057,82 @@ namespace Sass { } + //// std::cerr << "============================== HERE\n"; + + for (auto g1 : groups1) { + for (auto g : g1) { + //std::cerr << "g1: " << g->inspecter() << "\n"; + } + } + for (auto g2 : groups2) { + for (auto g : g2) { + //std::cerr << "g2: " << g->inspecter() << "\n"; + } + } + // Create junks from groups1 and groups2 - sass::vector>> - chunks = getChunks>( + sass::vector> + chunks = getChunks( groups1, groups2, {}, cmpChunkForEmptySequence); // Append chunks with inner arrays flattened choices.emplace_back(flattenInner(chunks)); // append all trailing selectors to choices - std::move(std::begin(trails), std::end(trails), - std::inserter(choices, std::end(choices))); + choices.insert(choices.end(), + std::make_move_iterator(trails.begin()), + std::make_move_iterator(trails.end())); // move all non empty items to the front, then erase the trailing ones choices.erase(std::remove_if(choices.begin(), choices.end(), checkForEmptyChild - >>), choices.end()); + >), choices.end()); + + for (auto g2 : choices) { + //std::cerr << "---\n"; + for (auto g3 : g2) { + //std::cerr << "[[[\n"; + for (auto g : g3) { + //std::cerr << "- choice: " << g->inspecter() << "\n"; + } + //std::cerr << "]]]\n"; + } + } + + auto perm = permutate(choices); + + for (auto g2 : perm) { + for (auto g3 : g2) { + for (auto g : g3) { + //std::cerr << "+ path: " << g->inspecter() << "\n"; + } + } + } + + sass::vector foobar; + for (auto path : perm) { + CplxSelComponentVector comps; + for (auto compis : path) { + for (auto compa : compis) { + comps.push_back(compa); + } + } + auto cply = SASS_MEMORY_NEW(ComplexSelector, base->pstate(), + lead, std::move(comps)); + foobar.push_back(cply); + } // permutate all possible paths through selectors - sass::vector> - results = flattenInner(permutate(choices)); + // auto qwe = flattenInner(perm); + for (auto g : foobar) { + //std::cerr << "flat: " << g->inspect() << "\n"; + } - return results; + return foobar; } // EO weaveParents - // ########################################################################## - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// } diff --git a/src/ast_selectors.cpp b/src/ast_selectors.cpp index f5a4867e9e..ba7df39357 100644 --- a/src/ast_selectors.cpp +++ b/src/ast_selectors.cpp @@ -1,1070 +1,1360 @@ -// sass.hpp must go before all system headers to get the -// __EXTENSIONS__ fix on Solaris. -#include "sass.hpp" +#include "ast_selectors.hpp" -#include "ast.hpp" #include "permutate.hpp" -#include "util_string.hpp" +#include "callstack.hpp" +#include "dart_helpers.hpp" +#include "ast_values.hpp" +#include "exceptions.hpp" +#include "sel_invisible.hpp" +#include "sel_useless.hpp" +#include "sel_bogus.hpp" +#include "cssize.hpp" + +#include "debugger.hpp" namespace Sass { ///////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////// - Selector::Selector(SourceSpan pstate) - : Expression(pstate), + Selector::Selector( + const SourceSpan& pstate) : + AstNode(pstate), hash_(0) - { concrete_type(SELECTOR); } - - Selector::Selector(const Selector* ptr) - : Expression(ptr), - hash_(ptr->hash_) - { concrete_type(SELECTOR); } - + {} - bool Selector::has_real_parent_ref() const - { - return false; - } + Selector::Selector( + const Selector* ptr) : + AstNode(ptr), + hash_(0) + {} ///////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////// - Selector_Schema::Selector_Schema(SourceSpan 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_) - { } + bool Selector::isUseless() const + { + IsUselessVisitor visitor; + return const_cast(this)->accept(&visitor); + } - unsigned long Selector_Schema::specificity() const + bool Selector::isInvisible() const { - return 0; + IsInvisibleVisitor visitor(true); + return const_cast(this)->accept(&visitor); } - size_t Selector_Schema::hash() const { - if (hash_ == 0) { - hash_combine(hash_, contents_->hash()); - } - return hash_; + bool Selector::isInvisibleOtherThanBogusCombinators() const + { + IsInvisibleVisitor visitor(false); + return const_cast(this)->accept(&visitor); } - bool Selector_Schema::has_real_parent_ref() const + bool Selector::isBogusOtherThanLeadingCombinator() 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; + IsBogusVisitor visitor(false); + return const_cast(this)->accept(&visitor); } - ///////////////////////////////////////////////////////////////////////// - ///////////////////////////////////////////////////////////////////////// + bool Selector::isBogusStrict() const + { + IsBogusVisitor visitor(true); + return const_cast(this)->accept(&visitor); + } - SimpleSelector::SimpleSelector(SourceSpan pstate, sass::string n) - : Selector(pstate), ns_(""), name_(n), has_ns_(false) + bool Selector::isBogusLenient() const { - size_t pos = n.find('|'); - // found some namespace - if (pos != sass::string::npos) { - has_ns_ = true; - ns_ = n.substr(0, pos); - name_ = n.substr(pos + 1); - } + IsBogusVisitor visitor(false); + return const_cast(this)->accept(&visitor); } - SimpleSelector::SimpleSelector(const SimpleSelector* ptr) - : Selector(ptr), - ns_(ptr->ns_), - name_(ptr->name_), - has_ns_(ptr->has_ns_) - { } - sass::string SimpleSelector::ns_name() const + SelectorList* SelectorList::assertNotBogus(const sass::string& name) { - if (!has_ns_) return name_; - else return ns_ + "|" + name_; + return this; } + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + SimpleSelector::SimpleSelector( + const SourceSpan& pstate, + const sass::string& name) : + Selector(pstate), + name_(name) + {} + + SimpleSelector::SimpleSelector( + const SourceSpan& pstate, + sass::string&& name) : + Selector(pstate), + name_(name) + {} + + SimpleSelector::SimpleSelector( + const SimpleSelector* ptr) : + Selector(ptr), + name_(ptr->name_) + {} + size_t SimpleSelector::hash() const { if (hash_ == 0) { + hash_start(hash_, typeid(this).hash_code()); hash_combine(hash_, name()); - hash_combine(hash_, (int)SELECTOR); - hash_combine(hash_, (int)simple_type()); - if (has_ns_) hash_combine(hash_, ns()); } return hash_; } - bool SimpleSelector::empty() const { - return ns().empty() && name().empty(); - } - - // namespace compare functions - bool SimpleSelector::is_ns_eq(const SimpleSelector& r) const + CompoundSelector* SimpleSelector::wrapInCompound() { - return has_ns_ == r.has_ns_ && ns_ == r.ns_; + return SASS_MEMORY_NEW(CompoundSelector, pstate(), { this }, false); } - // namespace query functions - bool SimpleSelector::is_universal_ns() const + ComplexSelector* SimpleSelector::wrapInComplex(SelectorCombinatorVector prefixes) { - return has_ns_ && ns_ == "*"; + auto* qwe = wrapInCompound()->wrapInComponent({}); + return SASS_MEMORY_NEW(ComplexSelector, pstate(), std::move(prefixes), { qwe }); } - bool SimpleSelector::is_empty_ns() const + ComplexSelector* CplxSelComponent::wrapInComplex(SelectorCombinatorVector prefixes) { - return !has_ns_ || ns_ == ""; + return SASS_MEMORY_NEW(ComplexSelector, pstate(), std::move(prefixes), { this }); } - bool SimpleSelector::has_empty_ns() const + ComplexSelector* CplxSelComponent::wrapInComplex2() { - return has_ns_ && ns_ == ""; + return SASS_MEMORY_NEW(ComplexSelector, pstate(), {}, { this }); } - bool SimpleSelector::has_qualified_ns() const + ComplexSelector* CompoundSelector::wrapInComplex3() { - return has_ns_ && ns_ != "" && ns_ != "*"; + auto comp = SASS_MEMORY_NEW(CplxSelComponent, pstate(), {}, this); + return SASS_MEMORY_NEW(ComplexSelector, pstate(), { comp }); } - // name query functions - bool SimpleSelector::is_universal() const + ComplexSelector* CompoundSelector::wrapInComplex(SelectorCombinatorVector prefixes, SelectorCombinatorVector tails) { - return name_ == "*"; + auto comp = SASS_MEMORY_NEW(CplxSelComponent, pstate(), std::move(tails), this); + return SASS_MEMORY_NEW(ComplexSelector, pstate(), std::move(prefixes), { comp }); } - bool SimpleSelector::has_placeholder() + CplxSelComponent* CompoundSelector::wrapInComponent(SelectorCombinatorVector postfixes) { - return false; + return SASS_MEMORY_NEW(CplxSelComponent, pstate(), std::move(postfixes), this); } - bool SimpleSelector::has_real_parent_ref() const - { - return false; - }; + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// - bool SimpleSelector::is_pseudo_element() const + SelectorNS::SelectorNS( + const SourceSpan& pstate, + sass::string&& name, + sass::string&& ns, + bool hasNs) : + SimpleSelector(pstate, + std::move(name)), + hasNs_(hasNs), + ns_(std::move(ns)) + {} + + SelectorNS::SelectorNS( + const SelectorNS* ptr) : + SimpleSelector(ptr), + hasNs_(ptr->hasNs_), + ns_(ptr->ns_) + {} + + size_t SelectorNS::hash() const { - return false; + if (hash_ == 0) { + hash_start(hash_, typeid(this).hash_code()); + hash_combine(hash_, SimpleSelector::hash()); + if (hasNs_) hash_combine(hash_, ns()); + } + return hash_; } - CompoundSelectorObj SimpleSelector::wrapInCompound() - { - CompoundSelectorObj selector = - SASS_MEMORY_NEW(CompoundSelector, pstate()); - selector->append(this); - return selector; - } - ComplexSelectorObj SimpleSelector::wrapInComplex() + ///////////////////////////////////////////////////////////////////////// + + // Up-casts the right hand side first to find specialization + bool SelectorNS::nsMatch(const SimpleSelector& rhs) const { - ComplexSelectorObj selector = - SASS_MEMORY_NEW(ComplexSelector, pstate()); - selector->append(wrapInCompound()); - return selector; + // if (hasNs() == false || isUniversalNs()) { + // return SimpleSelector::nsMatch(rhs); + // } + if (auto simple = rhs.isaSelectorNS()) { + return SelectorNS::nsMatch(*simple); + } + return !hasNs() || isUniversalNs(); } ///////////////////////////////////////////////////////////////////////// + // A placeholder selector. (e.g. `%foo`). This doesn't match any elements. + // It's intended to be extended using `@extend`. It's not a plain CSS + // selector — it should be removed before emitting a CSS document. ///////////////////////////////////////////////////////////////////////// - PlaceholderSelector::PlaceholderSelector(SourceSpan pstate, sass::string n) - : SimpleSelector(pstate, n) - { simple_type(PLACEHOLDER_SEL); } - PlaceholderSelector::PlaceholderSelector(const PlaceholderSelector* ptr) - : SimpleSelector(ptr) - { simple_type(PLACEHOLDER_SEL); } - unsigned long PlaceholderSelector::specificity() const - { - return Constants::Specificity_Base; - } - bool PlaceholderSelector::has_placeholder() { - return true; - } + PlaceholderSelector::PlaceholderSelector( + const SourceSpan& pstate, + const sass::string& name) : + SimpleSelector(pstate, name) + {} + + PlaceholderSelector::PlaceholderSelector( + const PlaceholderSelector* ptr) : + SimpleSelector(ptr) + {} ///////////////////////////////////////////////////////////////////////// + // A type selector. (e.g., `div`, `span` or `*`). + // This selects elements whose name equals the given name. ///////////////////////////////////////////////////////////////////////// - TypeSelector::TypeSelector(SourceSpan pstate, sass::string n) - : SimpleSelector(pstate, n) - { simple_type(TYPE_SEL); } - TypeSelector::TypeSelector(const TypeSelector* ptr) - : SimpleSelector(ptr) - { simple_type(TYPE_SEL); } + TypeSelector::TypeSelector( + const SourceSpan& pstate, + sass::string&& name, + sass::string&& ns, + bool hasNs) : + SelectorNS(pstate, + std::move(name), + std::move(ns), + hasNs) + {} + + TypeSelector::TypeSelector( + const TypeSelector* ptr) : + SelectorNS(ptr) + {} - unsigned long TypeSelector::specificity() const - { - if (name() == "*") return 0; - else return Constants::Specificity_Element; - } ///////////////////////////////////////////////////////////////////////// + // Class selectors -- i.e., .foo. ///////////////////////////////////////////////////////////////////////// - ClassSelector::ClassSelector(SourceSpan pstate, sass::string n) - : SimpleSelector(pstate, n) - { simple_type(CLASS_SEL); } - ClassSelector::ClassSelector(const ClassSelector* ptr) - : SimpleSelector(ptr) - { simple_type(CLASS_SEL); } + ClassSelector::ClassSelector( + const SourceSpan& pstate, + const sass::string& name) + : SimpleSelector(pstate, name) + {} - unsigned long ClassSelector::specificity() const - { - return Constants::Specificity_Class; - } + ClassSelector::ClassSelector( + const ClassSelector* ptr) : + SimpleSelector(ptr) + {} ///////////////////////////////////////////////////////////////////////// + // An ID selector (i.e. `#foo`). This selects elements + // whose `id` attribute exactly matches the given name. ///////////////////////////////////////////////////////////////////////// - IDSelector::IDSelector(SourceSpan pstate, sass::string n) - : SimpleSelector(pstate, n) - { simple_type(ID_SEL); } - IDSelector::IDSelector(const IDSelector* ptr) - : SimpleSelector(ptr) - { simple_type(ID_SEL); } + IDSelector::IDSelector( + const SourceSpan& pstate, + const sass::string& name) : + SimpleSelector(pstate, name) + {} - unsigned long IDSelector::specificity() const - { - return Constants::Specificity_ID; - } + IDSelector::IDSelector( + const IDSelector* ptr) : + SimpleSelector(ptr) + {} ///////////////////////////////////////////////////////////////////////// + // An attribute selector. This selects for elements + // with the given attribute, and optionally with a + // value matching certain conditions as well. ///////////////////////////////////////////////////////////////////////// - AttributeSelector::AttributeSelector(SourceSpan pstate, sass::string n, sass::string m, String_Obj v, char o) - : SimpleSelector(pstate, n), matcher_(m), value_(v), modifier_(o) - { simple_type(ATTRIBUTE_SEL); } - AttributeSelector::AttributeSelector(const AttributeSelector* ptr) - : SimpleSelector(ptr), - matcher_(ptr->matcher_), + AttributeSelector::AttributeSelector( + const SourceSpan& pstate, + struct QualifiedName&& name, + sass::string&& op, + sass::string&& value, + bool isIdentifier, + char modifier) : + SelectorNS(pstate, + std::move(name.name), + std::move(name.ns), + name.hasNs), + op_(std::move(op)), + value_(std::move(value)), + modifier_(modifier), + isIdentifier_(isIdentifier) + {} + + AttributeSelector::AttributeSelector( + const AttributeSelector* ptr) : + SelectorNS(ptr), + op_(ptr->op_), value_(ptr->value_), - modifier_(ptr->modifier_) - { simple_type(ATTRIBUTE_SEL); } - - size_t AttributeSelector::hash() const - { - if (hash_ == 0) { - hash_combine(hash_, SimpleSelector::hash()); - hash_combine(hash_, std::hash()(matcher())); - if (value_) hash_combine(hash_, value_->hash()); - } - return hash_; - } - - unsigned long AttributeSelector::specificity() const - { - return Constants::Specificity_Attr; - } + modifier_(ptr->modifier_), + isIdentifier_(ptr->isIdentifier_) + {} ///////////////////////////////////////////////////////////////////////// + // A pseudo-class or pseudo-element selector (e.g., `:content` + // or `:nth-child`). The semantics of a specific pseudo selector + // depends on its name. Some selectors take arguments, including + // other selectors. Sass manually encodes logic for each pseudo + // selector that takes a selector as an argument, to ensure that + // extension and other selector operations work properly. ///////////////////////////////////////////////////////////////////////// - PseudoSelector::PseudoSelector(SourceSpan pstate, sass::string name, bool element) - : SimpleSelector(pstate, name), - normalized_(Util::unvendor(name)), - argument_({}), - selector_({}), + PseudoSelector::PseudoSelector( + const SourceSpan& pstate, + const sass::string& name, + bool element) : + SimpleSelector(pstate, name), + normalized_(StringUtils::unvendor(name)), + argument_(), + selector_(), isSyntacticClass_(!element), isClass_(!element && !isFakePseudoElement(normalized_)) - { simple_type(PSEUDO_SEL); } - PseudoSelector::PseudoSelector(const PseudoSelector* ptr) - : SimpleSelector(ptr), + {} + + PseudoSelector::PseudoSelector( + const PseudoSelector* ptr) : + SimpleSelector(ptr), normalized_(ptr->normalized()), argument_(ptr->argument()), selector_(ptr->selector()), isSyntacticClass_(ptr->isSyntacticClass()), isClass_(ptr->isClass()) - { simple_type(PSEUDO_SEL); } - - // A pseudo-element is made of two colons (::) followed by the name. - // The `::` notation is introduced by the current document in order to - // establish a discrimination between pseudo-classes and pseudo-elements. - // For compatibility with existing style sheets, user agents must also - // accept the previous one-colon notation for pseudo-elements introduced - // in CSS levels 1 and 2 (namely, :first-line, :first-letter, :before and - // :after). This compatibility is not allowed for the new pseudo-elements - // introduced in this specification. - bool PseudoSelector::is_pseudo_element() const + { } + + + bool PseudoSelector::hasInvisible() const { - return isElement(); + return selector() && selector()->empty() && name() != "not"; } size_t PseudoSelector::hash() const { if (hash_ == 0) { - hash_combine(hash_, SimpleSelector::hash()); - if (selector_) hash_combine(hash_, selector_->hash()); - if (argument_) hash_combine(hash_, argument_->hash()); + hash_start(hash_, typeid(this).hash_code()); + hash_combine(hash_, argument_); + if (selector_) hash_combine( + hash_, selector_->hash()); } return hash_; } - unsigned long PseudoSelector::specificity() const - { - if (is_pseudo_element()) - return Constants::Specificity_Element; - return Constants::Specificity_Pseudo; - } - - PseudoSelectorObj PseudoSelector::withSelector(SelectorListObj selector) - { - PseudoSelectorObj pseudo = SASS_MEMORY_COPY(this); - pseudo->selector(selector); - return pseudo; - } - - bool PseudoSelector::empty() const - { + // Implement for cleanup phase + bool PseudoSelector::empty() const { // Only considered empty if selector is // available but has no items in it. - return selector() && selector()->empty(); + return argument_.empty() && name().empty() && + (selector() && selector()->empty()); + } + + bool PseudoSelector::isHostContext() const { + return isClass_ && name_ == "host-context" && + selector_ != nullptr; // && !selector_->empty(); } - void PseudoSelector::cloneChildren() + PseudoSelector* PseudoSelector::withSelector(SelectorList* selector) { - if (selector().isNull()) selector({}); - else selector(SASS_MEMORY_CLONE(selector())); + PseudoSelector* pseudo = SASS_MEMORY_COPY(this); + pseudo->selector(selector); + return pseudo; } - bool PseudoSelector::has_real_parent_ref() const { - if (!selector()) return false; - return selector()->has_real_parent_ref(); + const Selector* PseudoSelector::hasAnyExplicitParent() const { + if (selector_ == nullptr) return nullptr; + return selector_->getExplicitParent(); } ///////////////////////////////////////////////////////////////////////// + // 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 ///////////////////////////////////////////////////////////////////////// - SelectorList::SelectorList(SourceSpan pstate, size_t s) - : Selector(pstate), - Vectorized(s), - is_optional_(false) - { } - SelectorList::SelectorList(const SelectorList* ptr) - : Selector(ptr), - Vectorized(*ptr), - is_optional_(ptr->is_optional_) - { } - size_t SelectorList::hash() const + ComplexSelector::ComplexSelector( + const SourceSpan& pstate, + CplxSelComponentVector&& components) : + Selector(pstate), + Vectorized(std::move(components)), + chroots_(false), + hasPreLineFeed_(false), + hasLineBreak_(false), + leadingCombinators_({}) + {} + + ComplexSelector::ComplexSelector( + const SourceSpan& pstate, + const SelectorCombinatorVector& leadingCombinators, + const CplxSelComponentVector& components, + bool hasLineBreak) : + Selector(pstate), + Vectorized(components), + chroots_(hasLineBreak), + hasPreLineFeed_(hasLineBreak), + hasLineBreak_(hasLineBreak), + leadingCombinators_(leadingCombinators) + {} + + ComplexSelector::ComplexSelector( + const SourceSpan& pstate, + SelectorCombinatorVector&& leadingCombinators, + CplxSelComponentVector && components, + bool hasLineBreak) : + Selector(pstate), + Vectorized(std::move(components)), + chroots_(false), + hasPreLineFeed_(hasLineBreak), + hasLineBreak_(hasLineBreak), + leadingCombinators_(std::move(leadingCombinators)) + {} + + ComplexSelector::ComplexSelector( + const ComplexSelector* ptr, + bool childless) : + Selector(ptr), + Vectorized(ptr, childless), + chroots_(ptr->chroots_), + hasPreLineFeed_(ptr->hasPreLineFeed_), + hasLineBreak_(ptr->hasLineBreak_), + leadingCombinators_(ptr->leadingCombinators_) + {} + + + unsigned long ComplexSelector::specificity() const { - if (Selector::hash_ == 0) { - hash_combine(Selector::hash_, Vectorized::hash()); + if (specificity_ == 0xFFFFFFFF) { + specificity_ = 0; + for (const auto& component : elements()) { + specificity_ += component->selector()->specificity(); + } } - return Selector::hash_; + return specificity_; } - bool SelectorList::has_real_parent_ref() const + unsigned long ComplexSelector::maxSpecificity() const { - for (ComplexSelectorObj s : elements()) { - if (s && s->has_real_parent_ref()) return true; + if (maxSpecificity_ == 0xFFFFFFFF) { + maxSpecificity_ = 0; + for (const auto& component : elements()) { + maxSpecificity_ += component->selector()->maxSpecificity(); + } } - return false; + return maxSpecificity_; } - void SelectorList::cloneChildren() + unsigned long ComplexSelector::minSpecificity() const { - for (size_t i = 0, l = length(); i < l; i++) { - at(i) = SASS_MEMORY_CLONE(at(i)); + if (minSpecificity_ == 0xFFFFFFFF) { + minSpecificity_ = 0; + for (const auto& component : elements()) { + minSpecificity_ += component->selector()->minSpecificity(); + } } + return minSpecificity_; } - unsigned long SelectorList::specificity() const + ComplexSelector* ComplexSelector::produce() { + sass::vector copy; + for (CplxSelComponent* child : elements_) { + if (child->selector() == nullptr) continue; + CompoundSelectorObj asd = child->selector()->produce(); + copy.push_back(asd->wrapInComponent(child->combinators())); // ToDo combi + } + return SASS_MEMORY_NEW(ComplexSelector, + pstate_, std::move(copy)); + } + + bool ComplexSelector::hasInvisible() const { - return 0; + if (empty()) return true; + if (isBogusStrict()) return true; + for (const auto& component : elements()) { + if (component->hasInvisible()) return true; + } + return false; } - bool SelectorList::isInvisible() const + bool ComplexSelector::hasPlaceholder() const { - if (length() == 0) return true; - for (size_t i = 0; i < length(); i += 1) { - if (get(i)->isInvisible()) return true; + for (const auto& child : elements()) { + if (child->hasPlaceholder()) { + return true; + } } return false; } - ///////////////////////////////////////////////////////////////////////// - ///////////////////////////////////////////////////////////////////////// + //bool ComplexSelector::isUseless() const { + // return leadingCombinators_.size() > 1; + //} - ComplexSelector::ComplexSelector(SourceSpan pstate) - : Selector(pstate), - Vectorized(), - chroots_(false), - hasPreLineFeed_(false) - { + bool ComplexSelector::hasOneLeadingCombinators() const { + return leadingCombinators_.size() == 1; } - ComplexSelector::ComplexSelector(const ComplexSelector* ptr) - : Selector(ptr), - Vectorized(ptr->elements()), - chroots_(ptr->chroots()), - hasPreLineFeed_(ptr->hasPreLineFeed()) + + SelectorCombinator* ComplexSelector::getLeadingCombinator() const { + if (!leadingCombinators_.empty()) + return leadingCombinators_[0]; + return nullptr; } - void ComplexSelector::cloneChildren() + CompoundSelector* ComplexSelector::getSingleCompound() const { - for (size_t i = 0, l = length(); i < l; i++) { - at(i) = SASS_MEMORY_CLONE(at(i)); - } + if (elements_.size() != 1) return nullptr; + if (!leadingCombinators_.empty()) return nullptr; + auto& first = elements_.front(); + if (!first->combinators().empty()) return nullptr; + return first->selector(); } - unsigned long ComplexSelector::specificity() const + SelectorList* ComplexSelector::wrapInList() { - int sum = 0; - for (auto component : elements()) { - sum += component->specificity(); - } - return sum; + return SASS_MEMORY_NEW(SelectorList, pstate(), { this }); } - bool ComplexSelector::isInvisible() const + size_t ComplexSelector::hash() const { - if (length() == 0) return true; - for (size_t i = 0; i < length(); i += 1) { - if (CompoundSelectorObj compound = get(i)->getCompound()) { - if (compound->isInvisible()) return true; - } + if (Vectorized::hash_ == 0) { + Selector::hash_ = Vectorized::hash(); } - return false; + return Selector::hash_; } - bool ComplexSelector::isInvalidCss() const + const Selector* ComplexSelector::getExplicitParent() const { - for (size_t i = 0; i < length(); i += 1) { - if (CompoundSelectorObj compound = get(i)->getCompound()) { - if (compound->isInvalidCss()) return true; - } + const Selector* rv = nullptr; + for (const CplxSelComponentObj& component : elements()) { + rv = component->hasAnyExplicitParent(); + if (rv != nullptr) return rv; } - return false; + return nullptr; } - SelectorListObj ComplexSelector::wrapInList() + ///////////////////////////////////////////////////////////////////////// + // Base class for complex selector components + ///////////////////////////////////////////////////////////////////////// + + //CplxSelComponent::CplxSelComponent( + // const SourceSpan& pstate, + // bool hasPostLineBreak) : + // AstNode(pstate), + // hasPostLineBreak_(hasPostLineBreak) + //{} + + CplxSelComponent::CplxSelComponent( + const SourceSpan & pstate, + SelectorCombinatorVector&& combinators, + CompoundSelector* selector, + bool hasPostLineBreak) : + AstNode(pstate), + combinators_(std::move(combinators)), + selector_(selector), + hasPostLineBreak_(hasPostLineBreak) + {} + + CplxSelComponent::CplxSelComponent( + const CplxSelComponent* ptr) : + AstNode(ptr), + combinators_(ptr->combinators_), + selector_(ptr->selector_), + hasPostLineBreak_(ptr->hasPostLineBreak()) + {} + + size_t CplxSelComponent::hash() const { - SelectorListObj selector = - SASS_MEMORY_NEW(SelectorList, pstate()); - selector->append(this); - return selector; + return 123123; } - size_t ComplexSelector::hash() const + ///////////////////////////////////////////////////////////////////////// + // A specific combinator between compound selectors + ///////////////////////////////////////////////////////////////////////// + +// SelectorCombinator::SelectorCombinator( +// const SourceSpan& pstate, +// SelectorCombinator::Combinator combinator, +// bool hasPostLineBreak) : +// CplxSelComponent(pstate, hasPostLineBreak), +// combinator_(combinator) +// {} +// +// SelectorCombinator::SelectorCombinator( +// const SelectorCombinator* ptr) : +// CplxSelComponent(ptr), +// combinator_(ptr->combinator_) +// {} +// +// // Hash implementation is very simple +// size_t SelectorCombinator::hash() const +// { +// if (hash_ == 0) { +// hash_start(hash_, typeid(this).hash_code()); +// hash_combine(hash_, (size_t)combinator()); +// } +// return hash_; +// } + + ///////////////////////////////////////////////////////////////////////// + // A compound selector consists of multiple simple selectors. It will be + // either implicitly or explicitly connected to its parent sass selector. + // According to the specs we could also unify the tag selector into this, + // as AFAICT only one tag selector is ever allowed. Further we could free + // up the pseudo selectors from being virtual, as they must be last always. + // https://github.com/sass/libsass/pull/3101 + ///////////////////////////////////////////////////////////////////////// + + CompoundSelector::CompoundSelector( + const SourceSpan& pstate, + bool hasPostLineBreak) : + Selector(pstate), + withExplicitParent_(false), + hasPostLineBreak_(hasPostLineBreak) + {} + + CompoundSelector::CompoundSelector( + const SourceSpan& pstate, + sass::vector&& selectors, + bool hasPostLineBreak) : + Selector(pstate), + Vectorized(std::move(selectors)), + withExplicitParent_(false), + hasPostLineBreak_(hasPostLineBreak) + {} + + CompoundSelector::CompoundSelector( + const CompoundSelector* ptr, + bool childless) : + Selector(ptr), + Vectorized(ptr, childless), + withExplicitParent_(ptr->withExplicitParent()), + hasPostLineBreak_(ptr->hasPostLineBreak_) + {} + + size_t CompoundSelector::hash() const { - if (Selector::hash_ == 0) { - hash_combine(Selector::hash_, Vectorized::hash()); - // ToDo: this breaks some extend lookup - // hash_combine(Selector::hash_, chroots_); + if (Vectorized::hash_ == 0) { + Selector::hash_ = Vectorized::hash(); } return Selector::hash_; } - bool ComplexSelector::has_placeholder() const { - for (size_t i = 0, L = length(); i < L; ++i) { - if (get(i)->has_placeholder()) return true; + unsigned long CompoundSelector::specificity() const + { + if (specificity_ == 0xFFFFFFFF) { + specificity_ = 0; + for (const auto& component : elements()) { + specificity_ += component->specificity(); + } } - return false; + return specificity_; } - bool ComplexSelector::has_real_parent_ref() const + unsigned long CompoundSelector::maxSpecificity() const { - for (auto item : elements()) { - if (item->has_real_parent_ref()) return true; + if (maxSpecificity_ == 0xFFFFFFFF) { + maxSpecificity_ = 0; + for (const auto& simple : elements()) { + maxSpecificity_ += simple->maxSpecificity(); + } } - return false; + return maxSpecificity_; } - ///////////////////////////////////////////////////////////////////////// - ///////////////////////////////////////////////////////////////////////// + unsigned long CompoundSelector::minSpecificity() const + { + if (minSpecificity_ == 0xFFFFFFFF) { + minSpecificity_ = 0; + for (const auto& simple : elements()) { + minSpecificity_ += simple->minSpecificity(); + } + } + return minSpecificity_; + } - SelectorComponent::SelectorComponent(SourceSpan pstate, bool postLineBreak) - : Selector(pstate), - hasPostLineBreak_(postLineBreak) + CompoundSelector* CompoundSelector::produce() { + sass::vector copy; + for (SimpleSelector* child : elements_) { + copy.emplace_back(child); + } + return SASS_MEMORY_NEW(CompoundSelector, + pstate_, std::move(copy)); } - SelectorComponent::SelectorComponent(const SelectorComponent* ptr) - : Selector(ptr), - hasPostLineBreak_(ptr->hasPostLineBreak()) - { } + const Selector* CompoundSelector::hasAnyExplicitParent() const + { + if (withExplicitParent()) return this; + // ToDo: dart sass has another check? + // if (front->isaNameSpaceSelector()) { + // if (front->ns() != "") return false; + // } + const Selector* rv = nullptr; + for (const SimpleSelectorObj& s : elements()) { + if (s) rv = s->hasAnyExplicitParent(); + if (rv != nullptr) return rv; + } + return nullptr; + } - void SelectorComponent::cloneChildren() + bool CompoundSelector::hasPlaceholder() const { + if (size() == 0) return false; + for (const SimpleSelectorObj& ss : elements()) { + if (ss && ss->isaPlaceholderSelector()) return true; + } + return false; } - unsigned long SelectorComponent::specificity() const + bool CompoundSelector::hasInvisible() const { - return 0; + for (const SimpleSelectorObj& sel : elements()) { + if (sel && sel->hasInvisible()) return true; + } + return false; } - // Wrap the compound selector with a complex selector - ComplexSelector* SelectorComponent::wrapInComplex() + // Determine if given `this` is a sub-selector of `sub` + bool CompoundSelector::isSuperselectorOf(const CompoundSelector* sub) const { - auto complex = SASS_MEMORY_NEW(ComplexSelector, pstate()); - complex->append(this); - return complex; + return compoundIsSuperselector(this, sub); } ///////////////////////////////////////////////////////////////////////// + // Comma-separated selector groups. ///////////////////////////////////////////////////////////////////////// - SelectorCombinator::SelectorCombinator(SourceSpan pstate, SelectorCombinator::Combinator combinator, bool postLineBreak) - : SelectorComponent(pstate, postLineBreak), - combinator_(combinator) - { - } - SelectorCombinator::SelectorCombinator(const SelectorCombinator* ptr) - : SelectorComponent(ptr->pstate(), false), - combinator_(ptr->combinator()) - { } + SelectorList::SelectorList( + const SourceSpan& pstate, + sass::vector&& complexes) : + Selector(pstate), + Vectorized(std::move(complexes)) + {} - void SelectorCombinator::cloneChildren() - { - } + SelectorList::SelectorList( + const SelectorList* ptr, + bool childless) : + Selector(ptr), + Vectorized(ptr, childless) + {} - unsigned long SelectorCombinator::specificity() const + size_t SelectorList::hash() const { - return 0; + if (Vectorized::hash_ == 0) { + Selector::hash_ = Vectorized::hash(); + } + return Selector::hash_; } - ///////////////////////////////////////////////////////////////////////// - ///////////////////////////////////////////////////////////////////////// - - CompoundSelector::CompoundSelector(SourceSpan pstate, bool postLineBreak) - : SelectorComponent(pstate, postLineBreak), - Vectorized(), - hasRealParent_(false) + unsigned long SelectorList::maxSpecificity() const { + if (maxSpecificity_ == 0xFFFFFFFF) { + maxSpecificity_ = 0; + for (const auto& complex : elements()) { + maxSpecificity_ = std::max( + complex->maxSpecificity(), + maxSpecificity_); + } + } + return maxSpecificity_; } - CompoundSelector::CompoundSelector(const CompoundSelector* ptr) - : SelectorComponent(ptr), - Vectorized(*ptr), - hasRealParent_(ptr->hasRealParent()) - { } - size_t CompoundSelector::hash() const + unsigned long SelectorList::minSpecificity() const { - if (Selector::hash_ == 0) { - hash_combine(Selector::hash_, Vectorized::hash()); - hash_combine(Selector::hash_, hasRealParent_); + if (minSpecificity_ == 0xFFFFFFFF) { + minSpecificity_ = 0; + for (const auto& complex : elements()) { + maxSpecificity_ = std::min( + complex->minSpecificity(), + maxSpecificity_); + } } - return Selector::hash_; + return minSpecificity_; } - bool CompoundSelector::has_real_parent_ref() const + const Selector* SelectorList::getExplicitParent() 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; + const Selector* rv = nullptr; + for (const auto& s : elements()) { + rv = s->getExplicitParent(); + if (rv != nullptr) return rv; } - return false; + return nullptr; } - bool CompoundSelector::has_placeholder() const + Value* SelectorList::toValue() const { - if (length() == 0) return false; - for (SimpleSelectorObj ss : elements()) { - if (ss->has_placeholder()) return true; + ListObj list = SASS_MEMORY_NEW(List, + pstate(), {}, SASS_COMMA); + list->reserve(size()); + + for (ComplexSelector* complex : elements()) { + list->append(complex->toList()); } - return false; + if (list->size()) return list.detach(); + return SASS_MEMORY_NEW(Null, pstate()); } - void CompoundSelector::cloneChildren() + bool SelectorList::hasPlaceholder() const { - for (size_t i = 0, l = length(); i < l; i++) { - at(i) = SASS_MEMORY_CLONE(at(i)); + for (const auto& child : elements()) { + if (child->hasPlaceholder()) { + return true; + } } + return false; } - unsigned long CompoundSelector::specificity() const + bool CplxSelComponent::operator==(const CplxSelComponent& rhs) const { - int sum = 0; - for (size_t i = 0, L = length(); i < L; ++i) - { sum += get(i)->specificity(); } - return sum; + if (combinators_ != rhs.combinators_) return false; + if (selector_ && rhs.selector_) return *selector_ == *rhs.selector_; + return selector_ == nullptr && rhs.selector_ == nullptr; } - bool CompoundSelector::isInvisible() const + const Selector* CplxSelComponent::hasAnyExplicitParent() const { - for (size_t i = 0; i < length(); i += 1) { - if (!get(i)->isInvisible()) return false; - } - return true; + if (selector_ == nullptr) return nullptr; + return selector_->hasAnyExplicitParent(); } - bool CompoundSelector::isSuperselectorOf(const CompoundSelector* sub, sass::string wrapped) const + bool CplxSelComponent::hasPlaceholder() const { - CompoundSelector* rhs2 = const_cast(sub); - CompoundSelector* lhs2 = const_cast(this); - return compoundIsSuperselector(lhs2, rhs2, {}); + if (selector() == nullptr) return false; + for (const auto& child : selector()->elements()) { + if (child->hasPlaceholder()) { + return true; + } + } + return false; } ///////////////////////////////////////////////////////////////////////// + // Below are the resolveParentSelectors implementations ///////////////////////////////////////////////////////////////////////// - MediaRule::MediaRule(SourceSpan pstate, Block_Obj block) : - ParentStatement(pstate, block), - schema_({}) + + List* ComplexSelector::toList() const { - statement_type(MEDIA); + ListObj list = SASS_MEMORY_NEW(List, + pstate(), {}, SASS_SPACE); + if (leadingCombinators_.size() > 0) { + for (size_t i = 0; i < leadingCombinators_.size(); i++) { + sass::string prefix(leadingCombinators_[i]->toString()); + list->append(SASS_MEMORY_NEW(String, + pstate(), std::move(prefix))); + } + } + for (CplxSelComponent* component : elements()) { + if (component->selector()) { + sass::string prefix(component->selector()->inspect()); + list->append(SASS_MEMORY_NEW(String, + pstate(), std::move(prefix))); + } + for (auto combi : component->combinators()) { + sass::string prefix(combi->toString()); + list->append(SASS_MEMORY_NEW(String, + pstate(), std::move(prefix))); + } + } + return list.detach(); } - MediaRule::MediaRule(const MediaRule* ptr) : - ParentStatement(ptr), - schema_(ptr->schema_) - { - statement_type(MEDIA); + + template + sass::string VecToString(sass::vector exts) { + sass::string msg = "["; + for (auto& entry : exts) { + msg += entry->inspect(); + } + return msg + "]"; } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + SelectorCombinator::SelectorCombinator( + const SourceSpan& pstate, + SelectorPrefix combinator, + bool hasPostLineBreak) : + AstNode(pstate), + combinator_(combinator), + hasPostLineBreak_(hasPostLineBreak) + {} + + SelectorCombinator::SelectorCombinator( + const SelectorCombinator* ptr) : + AstNode(ptr), + combinator_(ptr->combinator_), + hasPostLineBreak_(ptr->hasPostLineBreak_) + {} + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + + + + + + + + ///////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////// - CssMediaRule::CssMediaRule(SourceSpan pstate, Block_Obj block) : - ParentStatement(pstate, block), - Vectorized() + sass::string CplxSelComponent::inspect() const { + sass::string text; + text += selector_->inspect(); + for (auto asd : combinators_) { + text += " " + asd->toString(); + } + return text; + } + + sass::string CplxSelComponent::inspecter() const { + sass::string text; + text += selector_->inspect(); + for (auto asd : combinators_) { + text += " " + asd->toString(); + } + return text; + } + + void CplxSelComponent::appendCombinators(SelectorCombinatorVector trails) { - statement_type(MEDIA); + combinators_.insert(combinators_.end(), + trails.begin(), trails.end()); } - CssMediaRule::CssMediaRule(const CssMediaRule* ptr) : - ParentStatement(ptr), - Vectorized(*ptr) + // Fully converted 16.01.2024 (untested) + CplxSelComponent* CplxSelComponent::withAdditionalCombinators( + const SelectorCombinatorVector& others) { - statement_type(MEDIA); + if (others.empty()) return this; + SelectorCombinatorVector merged(combinators_); + merged.insert(merged.end(), + others.begin(), others.end()); + return SASS_MEMORY_NEW(CplxSelComponent, + pstate(), std::move(merged), selector()); } - CssMediaQuery::CssMediaQuery(SourceSpan pstate) : - AST_Node(pstate), - modifier_(""), - type_(""), - features_() + // Fully converted 16.01.2024 (untested) + ComplexSelector* ComplexSelector::withAdditionalCombinators( + const SelectorCombinatorVector& combinators) { + if (combinators.empty()) return this; + CplxSelComponentVector components(elements_); + if (empty()) { + // Just add to existing leading combinators + SelectorCombinatorVector merged(leadingCombinators_); + merged.insert(merged.end(), combinators.begin(), combinators.end()); + return SASS_MEMORY_NEW(ComplexSelector, pstate_, + std::move(combinators), std::move(components)); + } + else { + // SelectorCombinatorVector merged(elements_.back()->combinators()); + components.back() = components.back()->withAdditionalCombinators(combinators); + return SASS_MEMORY_NEW(ComplexSelector, pstate_, + leadingCombinators_, std::move(components)); + } + + // SelectorCombinatorVector merged(combinators_); + // merged.insert(merged.end(), + // others.begin(), others.end()); + // return SASS_MEMORY_NEW(CplxSelComponent, + // pstate(), std::move(merged), selector()); } ///////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////// - bool CssMediaQuery::operator==(const CssMediaQuery& rhs) const - { - return type_ == rhs.type_ - && modifier_ == rhs.modifier_ - && features_ == rhs.features_; + // Fully converted 16.01.2024 (untested) + ComplexSelector* ComplexSelector::concatenate( + ComplexSelector* child, + const SourceSpan& span, + bool forceLineBreak) + { + // Create copies and move later + SelectorCombinatorVector leads; + CplxSelComponentVector merged; + // Case when child has no leading combinators + if (child->leadingCombinators().empty()) { + leads.insert(leads.end(), + this->leadingCombinators_.begin(), + this->leadingCombinators_.end()); + merged.insert(merged.end(), + this->begin(), this->end()); + merged.insert(merged.end(), + child->begin(), child->end()); + } + // Case when lha has some components + else if (size() > 0) { + leads.insert(leads.end(), + leadingCombinators_.begin(), + leadingCombinators_.end()); + // Append all but last items + merged.insert(merged.end(), + this->begin(), this->end() - 1); + // last with additional combinator + merged.push_back(last() + ->withAdditionalCombinators( + child->leadingCombinators())); + // merge in child componentes + merged.insert(merged.end(), + child->begin(), child->end()); + } + // Case when lhs has no componentes + else { + leads.insert(leads.end(), + this->leadingCombinators_.begin(), + this->leadingCombinators_.end()); + leads.insert(leads.end(), + child->leadingCombinators_.begin(), + child->leadingCombinators_.end()); + merged.insert(merged.end(), + child->begin(), child->end()); + } + // Return the concated complex selector + return SASS_MEMORY_NEW(ComplexSelector, + span, std::move(leads), std::move(merged), // avoid copy + hasLineBreak_ || child->hasLineBreak_ || forceLineBreak); } - // Implemented after dart-sass (maybe move to other class?) - CssMediaQuery_Obj CssMediaQuery::merge(CssMediaQuery_Obj& other) - { + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + - sass::string ourType = this->type(); - Util::ascii_str_tolower(&ourType); - sass::string theirType = other->type(); - Util::ascii_str_tolower(&theirType); - sass::string ourModifier = this->modifier(); - Util::ascii_str_tolower(&ourModifier); - sass::string theirModifier = other->modifier(); - Util::ascii_str_tolower(&theirModifier); - sass::string type; - sass::string modifier; - sass::vector features; - if (ourType.empty() && theirType.empty()) { - CssMediaQuery_Obj query = SASS_MEMORY_NEW(CssMediaQuery, pstate()); - sass::vector f1(this->features()); - sass::vector f2(other->features()); - features.insert(features.end(), f1.begin(), f1.end()); - features.insert(features.end(), f2.begin(), f2.end()); - query->features(features); - return query; + sass::vector ComplexSelector::resolveParentSelectors( + SelectorList* parent, BackTraces& traces, bool implicit_parent) + { + + const Selector* expl = getExplicitParent(); + // debug_ast(this, "test: "); + if (!parent && expl != nullptr) { + throw Exception::TopLevelParent(traces, expl->pstate()); } - if ((ourModifier == "not") != (theirModifier == "not")) { - if (ourType == theirType) { - sass::vector negativeFeatures = - ourModifier == "not" ? this->features() : other->features(); - sass::vector positiveFeatures = - ourModifier == "not" ? other->features() : this->features(); - - // If the negative features are a subset of the positive features, the - // query is empty. For example, `not screen and (color)` has no - // intersection with `screen and (color) and (grid)`. - // However, `not screen and (color)` *does* intersect with `screen and - // (grid)`, because it means `not (screen and (color))` and so it allows - // a screen with no color but with a grid. - if (listIsSubsetOrEqual(negativeFeatures, positiveFeatures)) { - return SASS_MEMORY_NEW(CssMediaQuery, pstate()); - } - else { - return {}; - } - } - else if (this->matchesAllTypes() || other->matchesAllTypes()) { - return {}; - } + sass::vector> selectors; - if (ourModifier == "not") { - modifier = theirModifier; - type = theirType; - features = other->features(); - } - else { - modifier = ourModifier; - type = ourType; - features = this->features(); - } + // bool cr = chroots(); + // bool he = hasExplicitParent(); + + // Check if selector should implicit get a parent + if (!chroots() && expl == nullptr) { + // Check if we should never connect to parent + if (!implicit_parent) { return { this }; } + // Otherwise add parent selectors at the beginning + if (parent) { selectors.emplace_back(parent->elements()); } } - 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 {}; - } + if (elements_.size() == 0) { + // Has no things to append + // Preserve component combinators + // std::cerr << "do me\n"; } - else { - if (this->matchesAllTypes()) { - modifier = theirModifier; - // Omit the type if either input query did, since that indicates that they - // aren't targeting a browser that requires "all and". - type = (other->matchesAllTypes() && ourType.empty()) ? "" : theirType; - sass::vector f1(this->features()); - sass::vector f2(other->features()); - features.insert(features.end(), f1.begin(), f1.end()); - features.insert(features.end(), f2.begin(), f2.end()); - } - else if (other->matchesAllTypes()) { - modifier = ourModifier; - type = ourType; - sass::vector f1(this->features()); - sass::vector f2(other->features()); - features.insert(features.end(), f1.begin(), f1.end()); - features.insert(features.end(), f2.begin(), f2.end()); - } - else if (ourType != theirType) { - return SASS_MEMORY_NEW(CssMediaQuery, pstate()); + + // bool first = true; + // Loop all items from the complex selector + // for (CplxSelComponent* component : this->elements()) { + for (size_t n = 0; n < this->size(); n++) { + + CplxSelComponent* component = elements_[n]; + + // std::cerr << "============\n"; + + // if (parent) std::cerr << "parent [" << parent->inspect() << "]\n"; + // std::cerr << "resolve [" << component->inspecter() << "]\n"; + if (CompoundSelector* compound = component->selector()) { + + SelectorCombinatorVector leads; + if (n == 0) leads.insert(leads.end(), + leadingCombinators_.begin(), + leadingCombinators_.end()); + auto tails(component->combinators()); + + //if (selectors.size() > 0) leads.clear(); + // loosing postfix combinators of component? + sass::vector complexes = + compound->resolveParentSelectors2(parent, traces, + leads, tails, implicit_parent); + + for (auto qwe : complexes) { + // std::cerr << "RESOL [" << qwe->inspect() << "]\n"; + } + + // for (auto sel : complexes) { sel->hasPreLineFeed(hasPreLineFeed()); } + if (complexes.size() > 0) { + selectors.emplace_back(complexes); + } } else { - modifier = ourModifier.empty() ? theirModifier : ourModifier; - type = ourType; - sass::vector f1(this->features()); - sass::vector f2(other->features()); - features.insert(features.end(), f1.begin(), f1.end()); - features.insert(features.end(), f2.begin(), f2.end()); + // component->hasPreLineFeed(hasPreLineFeed()); + selectors.push_back({ component->wrapInComplex(leadingCombinators_) }); } } - 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; - } + // std::cerr << "permutate now\n"; - CssMediaQuery::CssMediaQuery(const CssMediaQuery* ptr) : - AST_Node(*ptr), - modifier_(ptr->modifier_), - type_(ptr->type_), - features_(ptr->features_) - { - } + // Permutate through all paths + // for (auto s : selectors) { for (auto q : s) { std::cerr << "sel [" << q->inspect() << "]\n"; } } + selectors = permutateAlt(selectors); + // for (auto s : selectors) { for (auto q : s) { std::cerr << "perm [" << q->inspect() << "]\n"; } } - ///////////////////////////////////////////////////////////////////////// - // ToDo: finalize specificity implementation - ///////////////////////////////////////////////////////////////////////// + // Create final selectors from path permutations + sass::vector resolved; + for (sass::vector& append : selectors) { - size_t SelectorList::maxSpecificity() const - { - size_t specificity = 0; - for (auto complex : elements()) { - specificity = std::max(specificity, complex->maxSpecificity()); - } - return specificity; - } + if (append.empty()) continue; - size_t SelectorList::minSpecificity() const - { - size_t specificity = 0; - for (auto complex : elements()) { - specificity = std::min(specificity, complex->minSpecificity()); + ComplexSelectorObj front = SASS_MEMORY_COPY(append[0]); + // ToDo: this seems suspicious, why this logic? + if (hasPreLineFeed() && expl == nullptr) { + front->hasPreLineFeed(true); + } + // ToDo: remove once we know how to handle line feeds + // ToDo: currently a mash-up between ruby and dart sass + // if (has_real_parent_ref()) first->has_line_feed(false); + // first->has_line_break(first->has_line_break() || has_line_break()); + front->chroots(true); // has been resolved by now + + for (size_t i = 1; i < append.size(); i += 1) { + if (append[i]->hasPreLineFeed()) { + front->hasPreLineFeed(true); + } + for (auto tail : append[i]->elements()) { + // if (front->elements().size() > 0) { + // SelectorCombinatorVector trails + // = front->elements().back()->combinators(); + // // + // trails.insert(trails.end(), + // append[i]->leadingCombinators_.begin(), + // append[i]->leadingCombinators_.end()); + // + // front->elements().back()->combinators(trails); + // } + if (front->size() > 0 && append[i]->leadingCombinators_.size() > 0) { + front->elements().back() = SASS_MEMORY_NEW(CplxSelComponent, front->elements().back().ptr()); + front->elements().back()->appendCombinators(append[i]->leadingCombinators_); + } + front->elements().push_back(tail); + } + // first->concat(items[i]); + } + // debug_ast(first, "resolved: "); + // std::cerr << " + [" << first->inspect() << "]\n"; + resolved.emplace_back(front); } - return specificity; - } - size_t CompoundSelector::maxSpecificity() const - { - size_t specificity = 0; - for (auto simple : elements()) { - specificity += simple->maxSpecificity(); + if (elements_.size() == 0) { + // Has no things to append + // Preserve component combinators + for (size_t i = 0; i < resolved.size(); i++) { + if (resolved[i]->size() == 0) { + std::cerr << "more weird edge case\n"; + } + else { + resolved[i]->elements().back() = SASS_MEMORY_NEW(CplxSelComponent, resolved[i]->elements().back().ptr()); + resolved[i]->elements().back()->appendCombinators(leadingCombinators_); + } + } } - return specificity; - } - size_t CompoundSelector::minSpecificity() const - { - size_t specificity = 0; - for (auto simple : elements()) { - specificity += simple->minSpecificity(); + for (auto q : resolved) { + // std::cerr << "res => [" << q->inspect() << "]\n"; } - return specificity; - } - size_t ComplexSelector::maxSpecificity() const - { - size_t specificity = 0; - for (auto component : elements()) { - specificity += component->maxSpecificity(); - } - return specificity; - } + // std::cerr << "=> " << VecToString(resolved) << "\n"; - size_t ComplexSelector::minSpecificity() const - { - size_t specificity = 0; - for (auto component : elements()) { - specificity += component->minSpecificity(); - } - return specificity; + return resolved; } + // EO ComplexSelector::resolveParentSelectors ///////////////////////////////////////////////////////////////////////// - // ToDo: this might be done easier with new selector format ///////////////////////////////////////////////////////////////////////// + // + sass::vector - CompoundSelector::resolve_parent_refs(SelectorStack pstack, Backtraces& traces, bool implicit_parent) + CompoundSelector::resolveParentSelectors2( + SelectorList* parents, BackTraces& traces, + SelectorCombinatorVector prefixes, // from complex selector + SelectorCombinatorVector tails, // From compound component + bool implicit_parent) { - auto parent = pstack.back(); +// prefixes.clear(); +// tails.clear(); + + // must add prefixes to parent's last selector combinators + sass::vector rv; - for (SimpleSelectorObj simple : elements()) { - if (PseudoSelector * pseudo = Cast(simple)) { - if (SelectorList* sel = Cast(pseudo->selector())) { - if (parent && !parent->has_real_parent_ref()) { - pseudo->selector(sel->resolve_parent_refs( - pstack, traces, implicit_parent)); + // Missing this->combinators + + if (parents) { + for (SimpleSelectorObj simple : elements()) { + if (PseudoSelector* pseudo = simple->isaPseudoSelector()) { + if (SelectorList* sel = pseudo->selector()) { + auto asd = sel->resolveParentSelectors( + parents, traces, implicit_parent); + std::cerr << "Resolved [" << asd->inspect() << "]\n"; + pseudo->selector(asd); } } } } // Mix with parents from stack - if (hasRealParent()) { + // Equivalent to dart-sass parent selector tail + if (withExplicitParent()) { + if (parents == nullptr) return { this->wrapInComplex(prefixes, tails) }; + SASS_ASSERT(parents != nullptr, "Parent must be defined"); + + for (auto parent1 : parents->elements()) { + // The parent complex selector has a compound selector + if (parent1->size() == 0) { + // Can't insert parent that ends with a combinator + // where the parent selector is followed by something + //callStackFrame frame(traces, complex->last()->pstate()); + //if (size() > 0) { throw Exception::InvalidParent(parent, traces, this); } + // Just append ourself to results + //std::cerr << "have no parent\n"; + rv.emplace_back(wrapInComplex(prefixes, tails)); + } + else { + CplxSelComponent* cptail = parent1->last(); - 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); + if (cptail->combinators().size() > 0) { + callStackFrame frame(traces, cptail->combinators().back()->pstate()); + if (size() > 0) throw Exception::InvalidParent(parent1, traces, this); + } + + cptail = SASS_MEMORY_NEW(CplxSelComponent, cptail); + + // Add prefixes from incoming to parent +// cptail->appendCombinators(tails); + // ctail->combinators() + + CompoundSelector* ptail = cptail->selector(); + ptail = SASS_MEMORY_COPY(ptail); + + if (ptail && ptail->size() > 0) { + // Create copies to alter them + ComplexSelectorObj parent = SASS_MEMORY_COPY(parent1); // 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 (size() > 0 && ptail->size() > 0) { + SimpleSelector* front = first(); + auto simple_back = ptail->last(); + auto simple_front = front->isaTypeSelector(); + // If they are type/simple selectors ... if (simple_front && simple_back) { + // ... we can combine the names into one simple_back = SASS_MEMORY_COPY(simple_back); auto name = simple_back->name(); name += simple_front->name(); simple_back->name(name); - tail->elements().back() = simple_back; - tail->elements().insert(tail->end(), - begin() + 1, end()); + // Replace with modified simple selector + ptail->setLast(simple_back); + // Append rest of selector components + ptail->concat({ begin() + 1, end() }); } else { - tail->concat(this); + // Append us to parent + ptail->concat(this); } } else { - tail->concat(this); + // Append us to parent + ptail->concat(this); } - - complex->elements().back() = tail; + // Reset the parent selector tail with + // the combination of parent plus ourself + // complex->setLast(tail->wrapInComponent()); + SelectorCombinatorVector pre; + + // pre.insert(pre.end(), prefixes.begin(), prefixes.end()); + + // complex->elements().back() = SASS_MEMORY_NEW(CplxSelComponent, complex->last()); + //auto pres = complex->last()->combinators(); + //cp.insert(cp.end(), pres.begin(), pres.end()); + //cp.insert(cp.end(), tails.begin(), tails.end()); + cptail->selector(ptail); + // cptail->appendCombinators(tails); + parent->setLast(cptail); // Append to results - rv.push_back(complex); + + parent = parent->withAdditionalCombinators(tails); + prefixes.insert(prefixes.end(), + parent->leadingCombinators().begin(), + parent->leadingCombinators().end()); + parent->leadingCombinators(prefixes); + + rv.emplace_back(parent); } + // SelectorCombinator else { + //std::cerr << "last parent has only combinators\n"; // 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); + callStackFrame frame(traces, parent1->last()->pstate()); + if (size() > 0) { throw Exception::InvalidParent(parents, traces, this); } + // Just append ourself to results + rv.emplace_back(wrapInComplex(prefixes, tails)); } } } - } - // No parents + } + // No parent else { - // Create a new wrapper to wrap ourself - auto complex = SASS_MEMORY_NEW(ComplexSelector, pstate()); - // Just append ourself - complex->append(this); - // Append to results - rv.push_back(complex); + // Wrap and append compound selector + SelectorCombinatorVector trailing; + trailing.insert(trailing.end(), + tails.begin(), tails.end()); + //trailing.insert(trailing.end(), + // combinator().begin(), tails.end()); + + rv.emplace_back(wrapInComplex(prefixes, tails)); } - return rv; - - } + for (auto a : rv) { + // std::cerr << "final { " << a->inspect() << " }\n"; + } - bool cmpSimpleSelectors(SimpleSelector* a, SimpleSelector* b) - { - return (a->getSortOrder() < b->getSortOrder()); - } + return rv; - void CompoundSelector::sortChildren() - { - std::sort(begin(), end(), cmpSimpleSelectors); } + // EO CompoundSelector::resolveParentSelectors - bool CompoundSelector::isInvalidCss() const - { - size_t current = 0, next = 0; - for (const SimpleSelector* sel : elements()) { - next = sel->getSortOrder(); - // Must only have one type selector - if (current == 1 && next == 1) { - return true; - } - if (next < current) { - return true; - } - current = next; - } - return false; - } + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// - /* better return sass::vector? only - is empty container anyway? */ - SelectorList* ComplexSelector::resolve_parent_refs( - SelectorStack pstack, Backtraces& traces, bool implicit_parent) + SelectorList* SelectorList::resolveParentSelectors( + SelectorList* parent, BackTraces& traces, bool implicit_parent) { - - sass::vector> vars; - - auto parent = pstack.back(); - auto hasRealParent = has_real_parent_ref(); - - if (hasRealParent && !parent) { - throw Exception::TopLevelParent(traces, pstate()); + sass::vector> lists; + for (ComplexSelector* sel : elements()) { + lists.emplace_back(sel->resolveParentSelectors + (parent, traces, implicit_parent)); } - - if (!chroots() && parent) { - - if (!hasRealParent && !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 - sass::vector> res = permutateAlt(vars); - - // std::reverse(std::begin(res), std::end(res)); - - auto lst = SASS_MEMORY_NEW(SelectorList, pstate()); - for (auto items : res) { - if (items.size() > 0) { - ComplexSelectorObj first = SASS_MEMORY_COPY(items[0]); - first->hasPreLineFeed(first->hasPreLineFeed() || (!hasRealParent && hasPreLineFeed())); - // ToDo: remove once we know how to handle line feeds - // ToDo: currently a mashup between ruby and dart sass - // if (hasRealParent) 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; - + return SASS_MEMORY_NEW(SelectorList, pstate(), + flattenVertically(lists)); } + // EO SelectorList::resolveParentSelectors - 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(PlaceholderSelector); - IMPLEMENT_AST_OPERATORS(AttributeSelector); - IMPLEMENT_AST_OPERATORS(TypeSelector); - IMPLEMENT_AST_OPERATORS(ClassSelector); - IMPLEMENT_AST_OPERATORS(IDSelector); - IMPLEMENT_AST_OPERATORS(PseudoSelector); - IMPLEMENT_AST_OPERATORS(SelectorCombinator); - IMPLEMENT_AST_OPERATORS(CompoundSelector); - IMPLEMENT_AST_OPERATORS(ComplexSelector); - IMPLEMENT_AST_OPERATORS(SelectorList); } + + diff --git a/src/ast_selectors.hpp b/src/ast_selectors.hpp index bdc25b4422..6abfb2d073 100644 --- a/src/ast_selectors.hpp +++ b/src/ast_selectors.hpp @@ -1,382 +1,859 @@ -#ifndef SASS_AST_SEL_H -#define SASS_AST_SEL_H +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#ifndef SASS_AST_SELECTORS_HPP +#define SASS_AST_SELECTORS_HPP -// sass.hpp must go before all system headers to get the -// __EXTENSIONS__ fix on Solaris. -#include "sass.hpp" -#include "ast.hpp" +// sass.hpp must go before all system headers +// to get the __EXTENSIONS__ fix on Solaris. +#include "capi_sass.hpp" + +#include "ast_nodes.hpp" +#include "constants.hpp" +#include "visitor_selector.hpp" namespace Sass { ///////////////////////////////////////////////////////////////////////// - // Some helper functions + // Some helpers for superselector and weave parts ///////////////////////////////////////////////////////////////////////// bool compoundIsSuperselector( - const CompoundSelectorObj& compound1, - const CompoundSelectorObj& compound2, - const sass::vector& parents); + const CompoundSelector* compound1, + const CompoundSelector* compound2, + const CplxSelComponentVector& parents = {}); bool complexIsParentSuperselector( - const sass::vector& complex1, - const sass::vector& complex2); - - sass::vector> weave( - const sass::vector>& complexes); + const CplxSelComponentVector& complex1, + const CplxSelComponentVector& complex2); - sass::vector> weaveParents( - sass::vector parents1, - sass::vector parents2); + sass::vector weave( + const sass::vector& complexes, + bool forceLineBreak = false); - sass::vector unifyCompound( - const sass::vector& compound1, - const sass::vector& compound2); + // ToDo: What happens if we modify our parent? + sass::vector weaveParents( + ComplexSelector* prefix, ComplexSelector* base); - sass::vector> unifyComplex( - const sass::vector>& complexes); + sass::vector _unifyComplex( + sass::vector complexes, + const SourceSpan& pstate); - ///////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// // Abstract base class for CSS selectors. - ///////////////////////////////////////// - class Selector : public Expression { + ///////////////////////////////////////////////////////////////////////// + + class Selector : public AstNode, + public SelectorVisitable, + public SelectorVisitable + { protected: + + // Hash is only calculated once and afterwards the value + // must not be mutated, which is the case with how sass + // works, although we must be a bit careful not to alter + // any value that has already been added to a set or map. + // Must create a copy if you need to alter such an object. + // Selectors are mostly used as keys in @extend rules. mutable size_t hash_; + public: - Selector(SourceSpan pstate); - virtual ~Selector() = 0; - size_t hash() const override = 0; - virtual bool has_real_parent_ref() const; - // you should reset this to null on containers + + // Base value constructor + Selector(const SourceSpan& pstate); + + // Base copy constructor + Selector(const Selector* ptr); + + bool isUseless() const; + + bool isBogusStrict() const; + bool isBogusLenient() const; + + bool isInvisibleOtherThanBogusCombinators() const; + bool isBogusOtherThanLeadingCombinator() const; + + bool isInvisible() const; + + // To be implemented by specialization + virtual size_t hash() const = 0; virtual unsigned long specificity() const = 0; - // by default we return the regular specificity - // you must override this for all containers - virtual size_t maxSpecificity() const { return specificity(); } - virtual size_t minSpecificity() const { return specificity(); } - // dispatch to correct handlers - ATTACH_VIRTUAL_CMP_OPERATIONS(Selector) - ATTACH_VIRTUAL_AST_OPERATIONS(Selector) + // By default we return the regular specificity + // Override this for selectors with children + virtual unsigned long maxSpecificity() const { return specificity(); } + virtual unsigned long minSpecificity() const { return specificity(); } + + // Specialized by CompoundSelector + virtual bool hasPlaceholder() const { return false; } + + // Convert the selector to string, mostly for debugging + sass::string inspect(int precision = SassDefaultPrecision) const; + + // Returns if any compound selector has an explicit parent `&` selector. + // Only compound selectors are allowed to have this beside interpolations, + // which are handled very different and separately. Pseudo-selector like + // `:not` can also have an impact here, which is currently the sole use + // for having this as a virtual function. It is certainly questionable + // why a list returns true here if only one compound selector has it!? + virtual const Selector* hasAnyExplicitParent() const { return nullptr; } + + // Calls the appropriate visit method on [visitor]. + // Needed here to avoid ambiguity from base-classes!?? + virtual void accept(SelectorVisitor* visitor) override = 0; + virtual bool accept(SelectorVisitor* visitor) override = 0; + + // To be implemented by specialization + virtual bool operator==(const Selector& rhs) const = 0; + + // Base copy method with [childless] being void most of the times + virtual Selector* copy(SASS_MEMORY_ARGS bool childless = false) const = 0; + + // Declare up-casting methods + DECLARE_ISA_CASTER(IDSelector); + DECLARE_ISA_CASTER(TypeSelector); + DECLARE_ISA_CASTER(PseudoSelector); + DECLARE_ISA_CASTER(ClassSelector); + DECLARE_ISA_CASTER(AttributeSelector); + DECLARE_ISA_CASTER(PlaceholderSelector); + DECLARE_ISA_CASTER(SelectorNS); + DECLARE_ISA_CASTER(SimpleSelector); + DECLARE_ISA_CASTER(ComplexSelector); + //DECLARE_ISA_CASTER(SelectorCombinator); + DECLARE_ISA_CASTER(CompoundSelector); + DECLARE_ISA_CASTER(SelectorList); }; - inline Selector::~Selector() { } ///////////////////////////////////////////////////////////////////////// - // Interpolated selectors -- the interpolated String will be expanded and - // re-parsed into a normal selector class. + // Abstract base class for simple selectors. ///////////////////////////////////////////////////////////////////////// - class Selector_Schema final : public AST_Node { - ADD_PROPERTY(String_Schema_Obj, contents) - ADD_PROPERTY(bool, connect_parent); - // store computed hash - mutable size_t hash_; + + class SimpleSelector : public Selector + { + private: + + ADD_CONSTREF(sass::string, name); + public: - Selector_Schema(SourceSpan pstate, String_Obj c); - - bool has_real_parent_ref() const; - // selector schema is not yet a final selector, so we do not - // have a specificity for it yet. We need to - virtual unsigned long specificity() const; - size_t hash() const override; - ATTACH_AST_OPERATIONS(Selector_Schema) - ATTACH_CRTP_PERFORM_METHODS() + + // Value constructor + SimpleSelector( + const SourceSpan& pstate, + const sass::string& name); + + // Value constructor + SimpleSelector( + const SourceSpan& pstate, + sass::string&& name); + + // Copy constructor + SimpleSelector( + const SimpleSelector* ptr); + + // Wrap inside another selector type + ComplexSelector* wrapInComplex(SelectorCombinatorVector prefixes); + CompoundSelector* wrapInCompound(); + + // Implement hash functionality + virtual size_t hash() const override; + + // Implement for cleanup phase + virtual bool empty() const { + return name().empty(); + } + + // Unify simple selector with multiple simple selectors + virtual sass::vector unify( + const sass::vector& other); + + // Returns true if name equals '*' + bool isUniversal() const { + return name_ == "*"; + } + + // Checker if the name + virtual bool nsMatch(const SimpleSelector& r) const { return true; } + + virtual bool hasInvisible() const { return false; } + + virtual bool isSuperselector(SimpleSelector* other) const; + + virtual bool isSuperselectorAF(SimpleSelector* other) const; + + // This is a very interesting line, as it seems pointless, since the base class + // already marks this as an unimplemented interface methods, but by defining this + // line here, we make sure that callers know the return is a bit more specific. + virtual SimpleSelector* copy(SASS_MEMORY_ARGS bool childless = false) const override = 0; + + IMPLEMENT_ISA_CASTER(SimpleSelector); + }; - //////////////////////////////////////////// - // Abstract base class for simple selectors. - //////////////////////////////////////////// - class SimpleSelector : public Selector { - public: - enum Simple_Type { - ID_SEL, - TYPE_SEL, - CLASS_SEL, - PSEUDO_SEL, - ATTRIBUTE_SEL, - PLACEHOLDER_SEL, - }; - public: - HASH_CONSTREF(sass::string, ns) - HASH_CONSTREF(sass::string, name) - ADD_PROPERTY(Simple_Type, simple_type) - HASH_PROPERTY(bool, has_ns) + ///////////////////////////////////////////////////////////////////////// + // Base class for all selectors that support name-spaces + ///////////////////////////////////////////////////////////////////////// + + struct QualifiedName { + sass::string name; + sass::string ns; + bool hasNs; + }; + + class SelectorNS : public SimpleSelector + { + private: + + ADD_CONSTREF(bool, hasNs); + ADD_CONSTREF(sass::string, ns); + public: - SimpleSelector(SourceSpan pstate, sass::string n = ""); - // ordering within parent (peudos go last) - virtual int getSortOrder() const = 0; - virtual sass::string ns_name() const; - size_t hash() const override; - virtual bool empty() const; - // namespace compare functions - bool is_ns_eq(const SimpleSelector& r) const; - // namespace query functions - bool is_universal_ns() const; - bool is_empty_ns() const; - bool has_empty_ns() const; - bool has_qualified_ns() const; - // name query functions - bool is_universal() const; - virtual bool has_placeholder(); - - virtual ~SimpleSelector() = 0; - virtual CompoundSelector* unifyWith(CompoundSelector*); - - /* helper function for syntax sugar */ - virtual IDSelector* getIdSelector() { return NULL; } - virtual TypeSelector* getTypeSelector() { return NULL; } - virtual PseudoSelector* getPseudoSelector() { return NULL; } - - 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(); + // Value constructor + SelectorNS( + const SourceSpan& pstate, + sass::string&& name, + sass::string&& ns, + bool hasNs = false); + + // Copy constructor + SelectorNS( + const SelectorNS* ptr); + + // Implement hash functionality + virtual size_t hash() const override; + + // Implement for cleanup phase + virtual bool empty() const override { + return ns().empty() && SimpleSelector::empty(); + } + + // Returns true if namespaces match exactly + bool nsEqual(const SelectorNS& rhs) const { + return hasNs_ == rhs.hasNs_ && ns_ == rhs.ns_; + } + + // Returns true if namespaces are considered compatible + inline bool nsMatch(const SelectorNS& rhs) const { + return isUniversalNs() || nsEqual(rhs); + } + + // Returns true if namespace was explicitly set to '*' + inline bool isUniversalNs() const { + return hasNs_ && ns_ == "*"; + } + + // Up-casts the right hand side first to find specialization + bool nsMatch(const SimpleSelector& rhs) const override final; + + // This is a very interesting line, as it seems pointless, since the base class + // already marks this as an unimplemented interface methods, but by defining this + // line here, we make sure that callers know the return is a bit more specific. + virtual SelectorNS* copy(SASS_MEMORY_ARGS bool childless = false) const override = 0; + + IMPLEMENT_ISA_CASTER(SelectorNS); + + SelectorNS* unity(SelectorNS* rhs); }; - inline SimpleSelector::~SimpleSelector() { } ///////////////////////////////////////////////////////////////////////// - // Placeholder selectors (e.g., "%foo") for use in extend-only selectors. + // A placeholder selector. (e.g. `%foo`). This doesn't match any elements. + // It's intended to be extended using `@extend`. It's not a plain CSS + // selector — it should be removed before emitting a CSS document. ///////////////////////////////////////////////////////////////////////// - class PlaceholderSelector final : public SimpleSelector { + class PlaceholderSelector final : public SimpleSelector + { public: - PlaceholderSelector(SourceSpan pstate, sass::string n); - int getSortOrder() const override final { return 0; } - bool isInvisible() const override { return true; } - virtual unsigned long specificity() const override; - virtual bool has_placeholder() override; - bool operator==(const SimpleSelector& rhs) const override; - ATTACH_CMP_OPERATIONS(PlaceholderSelector) - ATTACH_AST_OPERATIONS(PlaceholderSelector) - ATTACH_CRTP_PERFORM_METHODS() + + // Value constructor + PlaceholderSelector( + const SourceSpan& pstate, + const sass::string& name); + + // Copy constructor + PlaceholderSelector( + const PlaceholderSelector* ptr); + + // Implement specialized specificity function + unsigned long specificity() const override final { + return Constants::Specificity::Base; + } + + virtual bool hasPlaceholder() const override final { return true; } + + + // Returns whether this is a private selector. + // That is, whether it begins with `-` or `_`. + bool isPrivate93() const { + if (name_[1] == 0) return false; + return name_[1] == Character::$minus + || name_[1] == Character::$underscore; + } + + IMPLEMENT_SEL_COPY_IGNORE(PlaceholderSelector); + IMPLEMENT_ACCEPT(void, Selector, PlaceholderSelector); + IMPLEMENT_ACCEPT(bool, Selector, PlaceholderSelector); + IMPLEMENT_EQ_OPERATOR(Selector, PlaceholderSelector); + + // Implement final up-casting method + IMPLEMENT_ISA_CASTER(PlaceholderSelector); }; - ///////////////////////////////////////////////////////////////////// - // Type selectors (and the universal selector) -- e.g., div, span, *. - ///////////////////////////////////////////////////////////////////// - class TypeSelector final : public SimpleSelector { + ///////////////////////////////////////////////////////////////////////// + // A type selector. (e.g., `div`, `span` or `*`). + // This selects elements whose name equals the given name. + ///////////////////////////////////////////////////////////////////////// + + class TypeSelector final : public SelectorNS + { public: - TypeSelector(SourceSpan pstate, sass::string n); - int getSortOrder() const override final { return 1; } - virtual unsigned long specificity() const override; - SimpleSelector* unifyWith(const SimpleSelector*); - CompoundSelector* unifyWith(CompoundSelector*) override; - TypeSelector* getTypeSelector() override { return this; } - bool operator==(const SimpleSelector& rhs) const final override; - ATTACH_CMP_OPERATIONS(TypeSelector) - ATTACH_AST_OPERATIONS(TypeSelector) - ATTACH_CRTP_PERFORM_METHODS() + + // Value constructor + TypeSelector( + const SourceSpan& pstate, + sass::string&& name, + sass::string&& ns, + bool hasNs = false); + + // Copy constructor + TypeSelector( + const TypeSelector* ptr); + + bool isSuperselectorAF(SimpleSelector* other) const override final; + + // Implement specialized specificity function + virtual unsigned long specificity() const override { + return isUniversal() ? 0 : Constants::Specificity::Element; + } + + sass::vector unifyUniversal(const sass::vector& compound); + + // Unify Type selector with multiple simple selectors + // CompoundSelector* unifyWith(CompoundSelector*); + virtual sass::vector unify( + const sass::vector& other) + override final; + + // Unify two simple selectors with each other + // SimpleSelector* unifyWith(const SimpleSelector*); + + IMPLEMENT_SEL_COPY_IGNORE(TypeSelector); + IMPLEMENT_ACCEPT(void, Selector, TypeSelector); + IMPLEMENT_ACCEPT(bool, Selector, TypeSelector); + IMPLEMENT_EQ_OPERATOR(Selector, TypeSelector); + + // Implement final up-casting method + IMPLEMENT_ISA_CASTER(TypeSelector); }; - //////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// // Class selectors -- i.e., .foo. - //////////////////////////////////////////////// - class ClassSelector final : public SimpleSelector { + ///////////////////////////////////////////////////////////////////////// + + class ClassSelector final : public SimpleSelector + { public: - ClassSelector(SourceSpan pstate, sass::string n); - int getSortOrder() const override final { return 2; } - virtual unsigned long specificity() const override; - bool operator==(const SimpleSelector& rhs) const final override; - ATTACH_CMP_OPERATIONS(ClassSelector) - ATTACH_AST_OPERATIONS(ClassSelector) - ATTACH_CRTP_PERFORM_METHODS() + + // Value constructor + ClassSelector( + const SourceSpan& pstate, + const sass::string& name); + + // Copy constructor + ClassSelector( + const ClassSelector* ptr); + + // Implement specialized specificity function + virtual unsigned long specificity() const override { + return Constants::Specificity::Class; + } + + IMPLEMENT_SEL_COPY_IGNORE(ClassSelector); + IMPLEMENT_ACCEPT(void, Selector, ClassSelector); + IMPLEMENT_ACCEPT(bool, Selector, ClassSelector); + IMPLEMENT_EQ_OPERATOR(Selector, ClassSelector); + + // Implement final up-casting method + IMPLEMENT_ISA_CASTER(ClassSelector); }; - //////////////////////////////////////////////// - // ID selectors -- i.e., #foo. - //////////////////////////////////////////////// - class IDSelector final : public SimpleSelector { + ///////////////////////////////////////////////////////////////////////// + // An ID selector (i.e. `#foo`). This selects elements + // whose `id` attribute exactly matches the given name. + ///////////////////////////////////////////////////////////////////////// + + class IDSelector final : public SimpleSelector + { public: - IDSelector(SourceSpan pstate, sass::string n); - int getSortOrder() const override final { return 2; } - virtual unsigned long specificity() const override; - CompoundSelector* unifyWith(CompoundSelector*) override; - IDSelector* getIdSelector() final override { return this; } - bool operator==(const SimpleSelector& rhs) const final override; - ATTACH_CMP_OPERATIONS(IDSelector) - ATTACH_AST_OPERATIONS(IDSelector) - ATTACH_CRTP_PERFORM_METHODS() + + // Value constructor + IDSelector( + const SourceSpan& pstate, + const sass::string& name); + + // Copy constructor + IDSelector( + const IDSelector* ptr); + + // Implement specialized specificity function + virtual unsigned long specificity() const override { + return Constants::Specificity::ID; + } + + // Unify ID selector with multiple simple selectors + // CompoundSelector* unifyWith(CompoundSelector*); + virtual sass::vector unify( + const sass::vector& other) + override final; + + IMPLEMENT_SEL_COPY_IGNORE(IDSelector); + IMPLEMENT_ACCEPT(void, Selector, IDSelector); + IMPLEMENT_ACCEPT(bool, Selector, IDSelector); + IMPLEMENT_EQ_OPERATOR(Selector, IDSelector); + + // Implement final up-casting method + IMPLEMENT_ISA_CASTER(IDSelector); }; - /////////////////////////////////////////////////// - // Attribute selectors -- e.g., [src*=".jpg"], etc. - /////////////////////////////////////////////////// - class AttributeSelector final : public SimpleSelector { - ADD_CONSTREF(sass::string, matcher) - // this cannot be changed to obj atm!!!!!!????!!!!!!! - ADD_PROPERTY(String_Obj, value) // might be interpolated - ADD_PROPERTY(char, modifier); + ///////////////////////////////////////////////////////////////////////// + // An attribute selector. This selects for elements + // with the given attribute, and optionally with a + // value matching certain conditions as well. + ///////////////////////////////////////////////////////////////////////// + + class AttributeSelector final : public SelectorNS + { + + // The operator that defines the semantics of [value]. + // If this is empty, this matches any element with the given property, + // regardless of this value. It's empty if and only if [value] is empty. + ADD_CONSTREF(sass::string, op); + + // An assertion about the value of [name]. + // The precise semantics of this string are defined by [op]. + // If this is `null`, this matches any element with the given property, + // regardless of this value. It's `null` if and only if [op] is `null`. + ADD_CONSTREF(sass::string, value); + + // The modifier which indicates how the attribute selector should be + // processed. See for example [case-sensitivity][] modifiers. + // [case-sensitivity]: https://www.w3.org/TR/selectors-4/#attribute-case + // If [op] is empty, this is always empty as well. + ADD_CONSTREF(char, modifier); + + // Defines if we parsed an identifier value. Dart-sass + // does this check again in serialize.visitAttributeSelector. + // We want to avoid this and do the check at parser stage. + ADD_CONSTREF(bool, isIdentifier); + public: - AttributeSelector(SourceSpan pstate, sass::string n, sass::string m, String_Obj v, char o = 0); - int getSortOrder() const override final { return 2; } - size_t hash() const override; - virtual unsigned long specificity() const override; - bool operator==(const SimpleSelector& rhs) const final override; - ATTACH_CMP_OPERATIONS(AttributeSelector) - ATTACH_AST_OPERATIONS(AttributeSelector) - ATTACH_CRTP_PERFORM_METHODS() + + // By value constructor + AttributeSelector( + const SourceSpan& pstate, + struct QualifiedName&& name, + sass::string&& op = "", + sass::string&& value = "", + bool isIdentifier = false, + char modifier = 0); + + // Copy constructor + AttributeSelector( + const AttributeSelector* ptr); + + // Implement specialized specificity function + virtual unsigned long specificity() const override { + return Constants::Specificity::Attr; + } + + IMPLEMENT_SEL_COPY_IGNORE(AttributeSelector); + IMPLEMENT_ACCEPT(void, Selector, AttributeSelector); + IMPLEMENT_ACCEPT(bool, Selector, AttributeSelector); + IMPLEMENT_EQ_OPERATOR(Selector, AttributeSelector); + + // Implement final up-casting method + IMPLEMENT_ISA_CASTER(AttributeSelector); }; - ////////////////////////////////////////////////////////////////// - // Pseudo selectors -- e.g., :first-child, :nth-of-type(...), etc. - ////////////////////////////////////////////////////////////////// - // Pseudo Selector cannot have any namespace? - class PseudoSelector final : public SimpleSelector { - ADD_PROPERTY(sass::string, normalized) - ADD_PROPERTY(String_Obj, argument) - ADD_PROPERTY(SelectorListObj, selector) - ADD_PROPERTY(bool, isSyntacticClass) - ADD_PROPERTY(bool, isClass) + ///////////////////////////////////////////////////////////////////////// + // A pseudo-class or pseudo-element selector (e.g., `:content` + // or `:nth-child`). The semantics of a specific pseudo selector + // depends on its name. Some selectors take arguments, including + // other selectors. Sass manually encodes logic for each pseudo + // selector that takes a selector as an argument, to ensure that + // extension and other selector operations work properly. + ///////////////////////////////////////////////////////////////////////// + + class PseudoSelector final : public SimpleSelector + { + + // Like [name], but without any vendor prefixes. + ADD_CONSTREF(sass::string, normalized); + + // The non-selector argument passed to this selector. This is + // `null` if there's no argument. If [argument] and [selector] + // are both non-`null`, the selector follows the argument. + ADD_CONSTREF(sass::string, argument); + + // The selector argument passed to this selector. This is `null` + // if there's no selector. If [argument] and [selector] are + // both non-`null`, the selector follows the argument. + ADD_CONSTREF(SelectorListObj, selector); + + // Whether this is syntactically a pseudo-class selector. This is + // the same as [isClass] unless this selector is a pseudo-element + // that was written syntactically as a pseudo-class (`:before`, + // `:after`, `:first-line`, or `:first-letter`). This is + // `true` if and only if [isSyntacticElement] is `false`. + ADD_CONSTREF(bool, isSyntacticClass); + + // Whether this is a pseudo-class selector. + // This is `true` if and only if [isPseudoElement] is `false`. + ADD_CONSTREF(bool, isClass); + public: - PseudoSelector(SourceSpan pstate, sass::string n, bool element = false); - int getSortOrder() const override final { return 3; } - virtual bool is_pseudo_element() const override; - size_t hash() const override; - bool empty() const override; + bool isElement() const { return !isClass_; } + + // Value constructor + PseudoSelector( + const SourceSpan& pstate, + const sass::string& name, + bool element = false); + + // Copy constructor + PseudoSelector( + const PseudoSelector* ptr); + + bool isSuperSelector(PseudoSelector* other) const; - bool has_real_parent_ref() const override; + bool isSuperselectorAF(SimpleSelector* other) const override final; + + // Returns true if there is a wrapped selector with an + // explicit `&` parent selector. Certainly questionable + // since the selector list may have compound selectors + // with and some without explicit parent selector!? + const Selector* hasAnyExplicitParent() const override final; + + bool hasInvisible() const override final; + + // Implement hash functionality + size_t hash() const override final; + + // Implement for cleanup phase + // Only considered empty if selector is + // available but has no items in it. + bool empty() const override final; // Whether this is a pseudo-element selector. // This is `true` if and only if [isClass] is `false`. - bool isElement() const { return !isClass(); } + // A pseudo-element is made of two colons (::) followed by the name. + // The `::` notation is introduced by the current document in order to + // establish a discrimination between pseudo-classes and pseudo-elements. + // For compatibility with existing style sheets, user agents must also + // accept the previous one-colon notation for pseudo-elements introduced + // in CSS levels 1 and 2 (namely, :first-line, :first-letter, :before and + // :after). This compatibility is not allowed for the new pseudo-elements + // introduced in this specification. + bool isPseudoElement() const { return !isClass(); } // Whether this is syntactically a pseudo-element selector. // This is `true` if and only if [isSyntacticClass] is `false`. bool isSyntacticElement() const { return !isSyntacticClass(); } - virtual unsigned long specificity() const override; - PseudoSelectorObj withSelector(SelectorListObj selector); + bool isHost() const { return isClass_ && name_ == "host"; } - CompoundSelector* unifyWith(CompoundSelector*) override; - PseudoSelector* getPseudoSelector() final override { return this; } - bool operator==(const SimpleSelector& rhs) const final override; - ATTACH_CMP_OPERATIONS(PseudoSelector) - ATTACH_AST_OPERATIONS(PseudoSelector) - void cloneChildren() override; - ATTACH_CRTP_PERFORM_METHODS() - }; + bool isHostContext() const; + + // Returns a new [PseudoSelector] based on ourself, + // but with the selector replaced with [selector]. + PseudoSelector* withSelector(SelectorList* selector); + + // Implement specialized specificity function + virtual unsigned long specificity() const override { + return isPseudoElement() + ? Constants::Specificity::Element + : Constants::Specificity::Pseudo; + } + // Unify Pseudo selector with multiple simple selectors + // CompoundSelector* unifyWith(CompoundSelector*); + virtual sass::vector unify( + const sass::vector& other) + override final; - //////////////////////////////////////////////////////////////////////////// + IMPLEMENT_SEL_COPY_IGNORE(PseudoSelector); + IMPLEMENT_ACCEPT(void, Selector, PseudoSelector); + IMPLEMENT_ACCEPT(bool, Selector, PseudoSelector); + IMPLEMENT_EQ_OPERATOR(Selector, PseudoSelector); + + // Implement final up-casting method + IMPLEMENT_ISA_CASTER(PseudoSelector); + }; + + ///////////////////////////////////////////////////////////////////////// // Complex Selectors are the most important class of selectors. // A Selector List consists of Complex Selectors (separated by comma) // Complex Selectors are itself a list of Compounds and Combinators // Between each item there is an implicit ancestor of combinator - //////////////////////////////////////////////////////////////////////////// - class ComplexSelector final : public Selector, public Vectorized { - ADD_PROPERTY(bool, chroots); + ///////////////////////////////////////////////////////////////////////// + + class ComplexSelector final : public Selector, + public Vectorized + { + + + ADD_CONSTREF(bool, chroots); // line break before list separator - ADD_PROPERTY(bool, hasPreLineFeed); + ADD_CONSTREF(bool, hasPreLineFeed); + + // line break after the selector + ADD_CONSTREF(bool, hasLineBreak); + + ADD_CONSTREF(SelectorCombinatorVector, leadingCombinators); + + // Calculate specificity only once + mutable unsigned long specificity_ = 0xFFFFFFFF; + mutable unsigned long maxSpecificity_ = 0xFFFFFFFF; + mutable unsigned long minSpecificity_ = 0xFFFFFFFF; + public: - ComplexSelector(SourceSpan pstate); + + // bool isUseless() const; + + bool hasOneLeadingCombinators() const; + SelectorCombinator* getLeadingCombinator() const; + + CompoundSelector* getSingleCompound() const; + + // const SelectorCombinatorVector& leadingCombinators() const; + + // Value constructor + ComplexSelector( + const SourceSpan& pstate, + CplxSelComponentVector&& components = {}); + + // Value constructor + ComplexSelector( + const SourceSpan& pstate, + const SelectorCombinatorVector& leadingCombinators, + const CplxSelComponentVector& components, + bool hasLineBreak = false); + + // Value constructor + ComplexSelector( + const SourceSpan& pstate, + SelectorCombinatorVector&& leadingCombinators, + CplxSelComponentVector&& components, + bool hasLineBreak = false); + + // Copy constructor + ComplexSelector( + const ComplexSelector* ptr, + bool childless = false); + + ComplexSelector* withAdditionalCombinators(const SelectorCombinatorVector& others); + + ComplexSelector* concatenate(ComplexSelector* child, const SourceSpan& span, bool forceLineBreak); // Returns true if the first components // is a compound selector and fulfills // a few other criteria. - bool isInvisible() const; - bool isInvalidCss() const; + bool hasInvisible() const; - size_t hash() const override; - void cloneChildren() override; - bool has_placeholder() const; - bool has_real_parent_ref() const override; + // Check if any of the selectors is/has a placeholder + bool hasPlaceholder() const override final; - SelectorList* resolve_parent_refs(SelectorStack pstack, Backtraces& traces, bool implicit_parent = true); - virtual unsigned long specificity() const override; + // Wrap inside another selector type + SelectorList* wrapInList(); - SelectorList* unifyWith(ComplexSelector* rhs); + // Implement hash functionality + size_t hash() const override final; + const Selector* getExplicitParent() const; + + // Convert to value list + List* toList() const; + + sass::vector + resolveParentSelectors( + SelectorList* parent, + BackTraces& traces, + bool implicit_parent = true); + + + // Unify two complex selectors with each other + SelectorList* unifyList(ComplexSelector* rhs); + + // Determine if given `this` is a sub-selector of `sub` bool isSuperselectorOf(const ComplexSelector* sub) const; - SelectorListObj wrapInList(); + // Specialize all specificity functions + unsigned long specificity() const override final; + unsigned long maxSpecificity() const override final; + unsigned long minSpecificity() const override final; + + ComplexSelector* produce(); + + IMPLEMENT_SEL_COPY_CHILDREN(ComplexSelector); + IMPLEMENT_ACCEPT(void, Selector, ComplexSelector); + IMPLEMENT_ACCEPT(bool, Selector, ComplexSelector); + IMPLEMENT_EQ_OPERATOR(Selector, ComplexSelector) - size_t maxSpecificity() const override; - size_t minSpecificity() const override; + // Implement final up-casting method + IMPLEMENT_ISA_CASTER(ComplexSelector); - 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; + ComplexSelector* withAdditionalComponent(CplxSelComponent* component, SourceSpan& span, bool forceLineBreak); - ATTACH_CMP_OPERATIONS(ComplexSelector) - ATTACH_AST_OPERATIONS(ComplexSelector) - ATTACH_CRTP_PERFORM_METHODS() }; - //////////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// // Base class for complex selector components - //////////////////////////////////////////////////////////////////////////// - class SelectorComponent : public Selector { + ///////////////////////////////////////////////////////////////////////// + + // Enumerate all possible selector combinators. There is some + // discrepancy with dart-sass. Opted to name them as in CSS33 + enum SelectorPrefix { CHILD /* > */, FOLLOWING /* ~ */, SIBLING /* + */ }; + + class CplxSelComponent : public AstNode + { + + ADD_CONSTREF(SelectorCombinatorVector, combinators); + + // This component's compound selector. + ADD_CONSTREF(CompoundSelectorObj, selector); + // line break after list separator - ADD_PROPERTY(bool, hasPostLineBreak) + ADD_CONSTREF(bool, hasPostLineBreak); + public: - SelectorComponent(SourceSpan pstate, bool postLineBreak = false); - size_t hash() const override = 0; - void cloneChildren() override; + sass::string inspect() const; + + sass::string inspecter() const; + + void appendCombinators(SelectorCombinatorVector trails); + + CplxSelComponent* withAdditionalCombinators(const SelectorCombinatorVector& combinators); + + // Value constructor + //CplxSelComponent( + // const SourceSpan& pstate, + // bool hasPostLineBreak = false); + + // Value constructor + CplxSelComponent( + const SourceSpan& pstate, + SelectorCombinatorVector&& combinators, + CompoundSelector* selector = nullptr, + bool hasPostLineBreak = false); + + // Copy constructor + CplxSelComponent( + const CplxSelComponent* ptr); + + // Implement hash functionality + virtual size_t hash() const; + //void cloneChildren(const Selector*) override; // By default we consider instances not empty virtual bool empty() const { return false; } - virtual bool has_placeholder() const = 0; - bool has_real_parent_ref() const override = 0; + virtual bool hasInvisible() const { return false; } - ComplexSelector* wrapInComplex(); + // Wrap inside another selector type + ComplexSelector* wrapInComplex2(); - size_t maxSpecificity() const override { return 0; } - size_t minSpecificity() const override { return 0; } + ComplexSelector* wrapInComplex(SelectorCombinatorVector); - virtual bool isCompound() const { return false; }; - virtual bool isCombinator() const { return false; }; + // virtual CplxSelComponent* produce() = 0; - /* 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; } + // To be implemented by specialization + bool operator==(const CplxSelComponent& rhs) const; + + const Selector* hasAnyExplicitParent() const; + + bool hasPlaceholder() const; + + + // This is a very interesting line, as it seems pointless, since the base class + // already marks this as an unimplemented interface methods, but by defining this + // line here, we make sure that callers know the return is a bit more specific. + // virtual CplxSelComponent* copy(SASS_MEMORY_ARGS bool childless = false) const override = 0; - virtual unsigned long specificity() const override; - bool operator==(const Selector& rhs) const override = 0; - ATTACH_VIRTUAL_CMP_OPERATIONS(SelectorComponent); - ATTACH_VIRTUAL_AST_OPERATIONS(SelectorComponent); }; - //////////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// // A specific combinator between compound selectors - //////////////////////////////////////////////////////////////////////////// - class SelectorCombinator final : public SelectorComponent { + ///////////////////////////////////////////////////////////////////////// + + class SelectorCombinator : public AstNode { + + ADD_CONSTREF(SelectorPrefix, combinator); + + ADD_CONSTREF(bool, hasPostLineBreak); + + public: + + // Value constructor + SelectorCombinator( + const SourceSpan& pstate, + SelectorPrefix combinator, + bool hasPostLineBreak = false); + + // Copy constructor + SelectorCombinator( + const SelectorCombinator* ptr); + + public: + + // Some convenient boolean checkers + bool isChild() const { return combinator_ == CHILD; } + bool isNextSibling() const { return combinator_ == SIBLING; } + bool isFollowingSibling() const { return combinator_ == FOLLOWING; } + + // Simple equality operators + bool operator==(const SelectorCombinator& rhs) const { + return combinator_ == rhs.combinator_; + } + bool operator!=(const SelectorCombinator& rhs) const { + return combinator_ != rhs.combinator_; + } + + const sass::string toString() const { + switch (combinator_) { + case CHILD: return ">"; + case SIBLING: return "+"; + case FOLLOWING: return "~"; + default: return "[NA]"; + } + } + + + }; + + /* + class SelectorCombinator final : public CplxSelComponent + { 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) + ADD_CONSTREF(Combinator, combinator); public: - SelectorCombinator(SourceSpan pstate, Combinator combinator, bool postLineBreak = false); - bool has_real_parent_ref() const override { return false; } - bool has_placeholder() const override { return false; } + // Value constructor + SelectorCombinator( + const SourceSpan& pstate, + Combinator combinator, + bool hasPostLineBreak = 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; }; + // Copy constructor + SelectorCombinator( + const SelectorCombinator* ptr); // Matches the right-hand selector if it's a direct child of the left- // hand selector in the DOM tree. Dart-sass also calls this `child` @@ -393,131 +870,202 @@ namespace Sass { // https://developer.mozilla.org/en-US/docs/Web/CSS/Adjacent_sibling_combinator bool isAdjacentCombinator() const { return combinator_ == ADJACENT; } // + - size_t maxSpecificity() const override { return 0; } - size_t minSpecificity() const override { return 0; } + // The combinators do not add anything to the specificity + unsigned long specificity() const override final { return 0; } + + // Implement hash functionality + size_t hash() const override final; - size_t hash() const override { - return std::hash()(combinator_); + CplxSelComponent* produce() override final { + return this; } - void cloneChildren() override; - virtual unsigned long specificity() const override; - bool operator==(const Selector& rhs) const override; - bool operator==(const SelectorComponent& rhs) const override; - - ATTACH_CMP_OPERATIONS(SelectorCombinator) - ATTACH_AST_OPERATIONS(SelectorCombinator) - ATTACH_CRTP_PERFORM_METHODS() + + IMPLEMENT_SEL_COPY_IGNORE(SelectorCombinator); + IMPLEMENT_ACCEPT(void, Selector, SelectorCombinator); + IMPLEMENT_ACCEPT(bool, Selector, SelectorCombinator); + IMPLEMENT_EQ_OPERATOR(Selector, SelectorCombinator); + + // Implement final up-casting method + IMPLEMENT_ISA_CASTER(SelectorCombinator); }; + */ + ///////////////////////////////////////////////////////////////////////// + // A compound selector consists of multiple simple selectors. It will be + // either implicitly or explicitly connected to its parent sass selector. + // According to the specs we could also unify the tag selector into this, + // as AFAICT only one tag selector is ever allowed. Further we could free + // up the pseudo selectors from being virtual, as they must be last always. + // https://github.com/sass/libsass/pull/3101 + ///////////////////////////////////////////////////////////////////////// + class CompoundSelector final : public Selector, public Vectorized + { + + // This is one of the most important flags for selectors. + // The `&` parent selector can only occur at the start of + // a compound selector. Interpolations `#{&}` are handle in + // another code-path. If an explicit parent is given we will + // not implicitly connect the selector to its scoped parent. + ADD_CONSTREF(bool, withExplicitParent); + + ADD_CONSTREF(bool, hasPostLineBreak); + + // Calculate specificity only once + mutable unsigned long specificity_ = 0xFFFFFFFF; + mutable unsigned long maxSpecificity_ = 0xFFFFFFFF; + mutable unsigned long minSpecificity_ = 0xFFFFFFFF; - //////////////////////////////////////////////////////////////////////////// - // A compound selector consists of multiple simple selectors - //////////////////////////////////////////////////////////////////////////// - class CompoundSelector final : public SelectorComponent, public Vectorized { - ADD_PROPERTY(bool, hasRealParent) public: - CompoundSelector(SourceSpan pstate, bool postLineBreak = false); - // Returns true if this compound selector - // fulfills various criteria. - bool isInvisible() const; + // Value Constructor + CompoundSelector( + const SourceSpan& pstate, + bool hasPostLineBreak = false); + + // Value move Constructor + CompoundSelector( + const SourceSpan& pstate, + sass::vector&& selectors, + bool hasPostLineBreak = false); + + // Copy constructor + CompoundSelector( + const CompoundSelector* ptr, + bool childless = false); + + // Returns true if any selector is invisible. + bool hasInvisible() const; - bool empty() const override { + // Implement for cleanup phase + // Dispatch to underlying list + bool empty() const { return Vectorized::empty(); } - size_t hash() const override; - CompoundSelector* unifyWith(CompoundSelector* rhs); + // Implement hash functionality + size_t hash() const override final; - /* helper function for syntax sugar */ - CompoundSelector* getCompound() final override { return this; } - const CompoundSelector* getCompound() const final override { return this; } + // Unify two lists of simple selectors (not in dart) + // CompoundSelector* unifyWith(CompoundSelector* rhs); - bool isSuperselectorOf(const CompoundSelector* sub, sass::string wrapped = "") const; + const Selector* hasAnyExplicitParent() const override final; - void cloneChildren() override; - bool has_real_parent_ref() const override; - bool has_placeholder() const override; - sass::vector resolve_parent_refs(SelectorStack pstack, Backtraces& traces, bool implicit_parent = true); + bool hasPlaceholder() const override final; - virtual bool isCompound() const override { return true; }; - virtual unsigned long specificity() const override; + // Resolve parents and form the final selector + sass::vector + resolveParentSelectors2( + SelectorList* parent, + BackTraces& traces, + SelectorCombinatorVector prefixes, + SelectorCombinatorVector tails, + bool implicit_parent = true); - size_t maxSpecificity() const override; - size_t minSpecificity() const override; + // Determine if given `this` is a sub-selector of `sub` + bool isSuperselectorOf(const CompoundSelector* sub) const; - bool operator==(const Selector& rhs) const override; + // Specialize all specificity functions + unsigned long specificity() const override final; + unsigned long maxSpecificity() const override final; + unsigned long minSpecificity() const override final; - bool operator==(const SelectorComponent& rhs) const override; + CompoundSelector* produce(); - bool operator==(const SelectorList& rhs) const; - bool operator==(const ComplexSelector& rhs) const; - bool operator==(const SimpleSelector& rhs) const; + ComplexSelector* wrapInComplex3(); - void sortChildren(); - bool isInvalidCss() const; + ComplexSelector* wrapInComplex(SelectorCombinatorVector prefixes, SelectorCombinatorVector tails); - ATTACH_CMP_OPERATIONS(CompoundSelector) - ATTACH_AST_OPERATIONS(CompoundSelector) - ATTACH_CRTP_PERFORM_METHODS() + CplxSelComponent* wrapInComponent(SelectorCombinatorVector postfixes); + + IMPLEMENT_SEL_COPY_CHILDREN(CompoundSelector); + IMPLEMENT_ACCEPT(void, Selector, CompoundSelector); + IMPLEMENT_ACCEPT(bool, Selector, CompoundSelector); + IMPLEMENT_EQ_OPERATOR(Selector, CompoundSelector); + + // Implement final up-casting method + IMPLEMENT_ISA_CASTER(CompoundSelector); }; - /////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// // Comma-separated selector groups. - /////////////////////////////////// - class SelectorList final : public Selector, public Vectorized { + ///////////////////////////////////////////////////////////////////////// + class SelectorList final : public Selector, + public Vectorized + { private: - // maybe we have optional flag - // ToDo: should be at ExtendRule? - ADD_PROPERTY(bool, is_optional) + + // Calculate specificity only once + mutable unsigned long maxSpecificity_ = 0xFFFFFFFF; + mutable unsigned long minSpecificity_ = 0xFFFFFFFF; + public: - SelectorList(SourceSpan pstate, size_t s = 0); - sass::string type() const override { return "list"; } - size_t hash() const override; + SelectorList* assertNotBogus(const sass::string& name); + + // Value move constructor + SelectorList( + const SourceSpan& pstate, + sass::vector&& = {}); + + // Copy constructor + SelectorList(const SelectorList* ptr, + bool childless = false); + + // Implement hash functionality + size_t hash() const override final; + + // Unify two selector lists with each other SelectorList* unifyWith(SelectorList*); - // Returns true if all complex selectors - // can have real parents, meaning every - // first component does allow for it - bool isInvisible() const; + const Selector* getExplicitParent() const; - void cloneChildren() override; - bool has_real_parent_ref() const override; - SelectorList* resolve_parent_refs(SelectorStack pstack, Backtraces& traces, bool implicit_parent = true); - virtual unsigned long specificity() const override; + // Convert to `List` or `Null` + Value* toValue() const; + SelectorList* resolveParentSelectors( + SelectorList* parent, + BackTraces& traces, + bool implicit_parent = true); + + // Check if any of the selectors is/has a placeholder + bool hasPlaceholder() const override final; + + // Determine if given `this` is a sub-selector of `sub` bool isSuperselectorOf(const SelectorList* sub) const; - size_t maxSpecificity() const override; - size_t minSpecificity() const override; + // This implementation is not available, don't call + unsigned long specificity() const override final { + throw std::runtime_error("specificity not implemented"); + } - bool operator==(const Selector& rhs) const override; - bool operator==(const ComplexSelector& rhs) const; - bool operator==(const CompoundSelector& rhs) const; - bool operator==(const SimpleSelector& rhs) const; - // Selector Lists can be compared to comma lists - bool operator==(const Expression& rhs) const override; + // Specialize min and max specificity functions + unsigned long maxSpecificity() const override final; + unsigned long minSpecificity() const override final; + + SelectorList* produce() { + sass::vector copy; + for (ComplexSelector* child : elements_) { + copy.emplace_back(child->produce()); + } + return SASS_MEMORY_NEW(SelectorList, + pstate_, std::move(copy)); + } - ATTACH_CMP_OPERATIONS(SelectorList) - ATTACH_AST_OPERATIONS(SelectorList) - ATTACH_CRTP_PERFORM_METHODS() - }; + sass::string toString() const { + return toValue()->toCss(); + } - //////////////////////////////// - // The Sass `@extend` directive. - //////////////////////////////// - class ExtendRule final : public Statement { - ADD_PROPERTY(bool, isOptional) - // This should be a simple selector only! - ADD_PROPERTY(SelectorListObj, selector) - ADD_PROPERTY(Selector_Schema_Obj, schema) - public: - ExtendRule(SourceSpan pstate, SelectorListObj s); - ExtendRule(SourceSpan pstate, Selector_Schema_Obj s); - ATTACH_AST_OPERATIONS(ExtendRule) - ATTACH_CRTP_PERFORM_METHODS() + IMPLEMENT_SEL_COPY_CHILDREN(SelectorList); + IMPLEMENT_ACCEPT(void, Selector, SelectorList); + IMPLEMENT_ACCEPT(bool, Selector, SelectorList); + IMPLEMENT_EQ_OPERATOR(Selector, SelectorList); + + // Implement final up-casting method + IMPLEMENT_ISA_CASTER(SelectorList); }; + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + } #endif diff --git a/src/ast_statements.cpp b/src/ast_statements.cpp new file mode 100644 index 0000000000..7cc06c5ba4 --- /dev/null +++ b/src/ast_statements.cpp @@ -0,0 +1,644 @@ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#include "ast_statements.hpp" + +#include "ast_supports.hpp" +#include "exceptions.hpp" +#include "compiler.hpp" + +namespace Sass { + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + WithConfig::WithConfig( + WithConfig* pwconfig, + sass::vector configs, + bool hasConfig, + bool hasShowFilter, + bool hasHideFilter, + std::set varFilters, + std::set callFilters, + const sass::string& prefix) : + parent(pwconfig), + hasConfig(hasConfig), + hasShowFilter(hasShowFilter), + hasHideFilter(hasHideFilter), + varFilters(varFilters), + callFilters(callFilters), + prefix(prefix) + { + // Populate config map from vector + // Duplicate entries are overwritten + for (auto cfgvar : configs) { + config[cfgvar.name] = cfgvar; + } + } + + void WithConfig::finalize(Logger& logger) + { + // Check if everything was consumed + for (auto cfgvar : config) { + if (cfgvar.second.wasAssigned == false) { + if (cfgvar.second.isGuarded41 == false) { + callStackFrame csf(logger, cfgvar.second.pstate); + throw Exception::RuntimeException(logger, "$" + + cfgvar.second.name + " was not declared " + "with !default in the @used module."); + } + } + } + } + + WithConfigVar* WithConfig::getCfgVar(const EnvKey& name) + { + + EnvKey key(name); + WithConfig* withcfg = this; + while (withcfg) { + // Check if we should apply any filtering first + if (withcfg->hasHideFilter) { + if (withcfg->varFilters.count(key.norm())) { + break; + } + } + if (withcfg->hasShowFilter) { + if (!withcfg->varFilters.count(key.norm())) { + break; + } + } + // Then try to find the named item + auto varcfg = withcfg->config.find(key); + if (varcfg != withcfg->config.end()) { + // Found an unguarded value + if (!varcfg->second.isGuarded41) { + if (!varcfg->second.isNull()) { + varcfg->second.wasAssigned = true; + return &varcfg->second; + } + } + } + // Should we apply some prefixes + if (!withcfg->prefix.empty()) { + sass::string prefix = withcfg->prefix; + key = EnvKey(prefix + key.orig()); + } + withcfg = withcfg->parent; + } + // Since we found no unguarded value we can assume + // the full stack only contains guarded values. + // Therefore they overwrite all each other. + withcfg = this; key = name; + WithConfigVar* guarded = nullptr; + while (withcfg) { + // Check if we should apply any filtering first + if (withcfg->hasHideFilter) { + if (withcfg->varFilters.count(key.norm())) { + break; + } + } + if (withcfg->hasShowFilter) { + if (!withcfg->varFilters.count(key.norm())) { + break; + } + } + // Then try to find the named item + auto varcfg = withcfg->config.find(key); + if (varcfg != withcfg->config.end()) { + varcfg->second.wasAssigned = true; + if (!guarded) guarded = &varcfg->second; + } + // Should we apply some prefixes + if (!withcfg->prefix.empty()) { + sass::string prefix = withcfg->prefix; + key = EnvKey(prefix + key.orig()); + } + withcfg = withcfg->parent; + } + return guarded; + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + ParentStatement::ParentStatement( + SourceSpan&& pstate, + StatementVector&& children, + EnvRefs* idxs) : + Statement(std::move(pstate)), + Vectorized(std::move(children)), + Env(idxs) + {} + + ParentStatement::ParentStatement( + const SourceSpan& pstate, + StatementVector&& children, + EnvRefs* idxs) : + Statement(std::move(pstate)), + Vectorized(std::move(children)), + Env(idxs) + {} + + // Returns whether we have a child content block + bool ParentStatement::hasContent() const + { + if (Statement::hasContent()) return true; + for (const StatementObj& child : elements_) { + if (child->hasContent()) return true; + } + return false; + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + StyleRule::StyleRule( + SourceSpan&& pstate, + Interpolation* interpolation, + EnvRefs* idxs, + StatementVector&& children) : + ParentStatement( + std::move(pstate), + std::move(children), + idxs), + interpolation_(interpolation) + {} + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + Declaration::Declaration( + SourceSpan&& pstate, + Interpolation* name, + Expression* value, + bool is_custom_property, + StatementVector&& children) : + ParentStatement( + std::move(pstate), + std::move(children), + nullptr), + name_(name), + value_(value), + is_custom_property_(is_custom_property) + {} + + bool Declaration::isCustomProperty() const + { + if (name_.isNull()) return false; + const sass::string& plain = name_->getInitialPlain(); + return StringUtils::startsWith(plain, "--", 2); + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + ForRule::ForRule( + SourceSpan&& pstate, + const EnvKey& varname, + Expression* lower_bound, + Expression* upper_bound, + bool is_inclusive, + EnvRefs* idxs, + StatementVector&& children) : + ParentStatement( + std::move(pstate), + std::move(children), + idxs), + varname_(varname), + lower_bound_(lower_bound), + upper_bound_(upper_bound), + is_inclusive_(is_inclusive) + {} + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + EachRule::EachRule( + SourceSpan&& pstate, + const EnvKeys& variables, + Expression* expressions, + EnvRefs* idxs, + StatementVector&& children) : + ParentStatement( + std::move(pstate), + std::move(children), + idxs), + variables_(variables), + expressions_(expressions) + {} + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + WhileRule::WhileRule( + SourceSpan&& pstate, + Expression* condition, + EnvRefs* idxs, + StatementVector&& children) : + ParentStatement( + std::move(pstate), + std::move(children), + idxs), + condition_(condition) + {} + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + MediaRule::MediaRule( + SourceSpan&& pstate, + Interpolation* query, + EnvRefs* idxs, + StatementVector&& children) : + ParentStatement( + std::move(pstate), + std::move(children), + idxs), + query_(query) + {} + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + AtRule::AtRule( + SourceSpan&& pstate, + Interpolation* name, + Interpolation* value, + EnvRefs* idxs, + bool isChildless, + StatementVector&& children) : + ParentStatement( + std::move(pstate), + std::move(children), + idxs), + name_(name), + value_(value), + isChildless_(isChildless) + {} + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + AtRootRule::AtRootRule( + SourceSpan&& pstate, + Interpolation* query, + EnvRefs* idxs, + StatementVector&& children) : + ParentStatement( + std::move(pstate), + std::move(children), + idxs), + query_(query) + {} + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + IfRule::IfRule( + SourceSpan&& pstate, + EnvRefs* idxs, + StatementVector&& children, + Expression* predicate, + IfRule* alternative) : + ParentStatement( + std::move(pstate), + std::move(children), + idxs), + predicate_(predicate), + alternative_(alternative) + {} + + // Also check alternative for content block + bool IfRule::hasContent() const + { + if (ParentStatement::hasContent()) return true; + return alternative_ && alternative_->hasContent(); + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + SupportsRule::SupportsRule( + SourceSpan&& pstate, + SupportsCondition* condition, + EnvRefs* idxs, + StatementVector&& children) : + ParentStatement( + std::move(pstate), + std::move(children), + idxs), + condition_(condition) + {} + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + CallableDeclaration::CallableDeclaration( + SourceSpan&& pstate, + const EnvKey& name, + CallableSignature* arguments, + StatementVector&& children, + SilentComment* comment, + EnvRefs* idxs) : + ParentStatement( + std::move(pstate), + std::move(children), + idxs), + name_(name), + comment_(comment), + arguments_(arguments) + {} + + CallableDeclaration::CallableDeclaration( + const SourceSpan& pstate, + const EnvKey& name, + CallableSignature* arguments, + StatementVector&& children, + SilentComment* comment, + EnvRefs* idxs) : + ParentStatement( + pstate, + std::move(children), + idxs), + name_(name), + comment_(comment), + arguments_(arguments) + {} + + bool CallableDeclaration::operator==(const CallableDeclaration & rhs) const + { + return name_ == rhs.name_; + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + IncludeRule::IncludeRule( + SourceSpan&& pstate, + const EnvKey& name, + const sass::string& ns, + CallableArguments* arguments, + ContentBlock* content) : + Statement(std::move(pstate)), + arguments_(arguments), + ns_(ns), + name_(name), + content_(content) + {} + + bool IncludeRule::hasContent() const + { + return !content_.isNull(); + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + ContentBlock::ContentBlock( + SourceSpan&& pstate, + CallableSignature* arguments, + EnvRefs* idxs, + StatementVector&& children, + SilentComment* comment) : + CallableDeclaration( + std::move(pstate), + Keys::contentRule, + arguments, + std::move(children), + comment, idxs) + {} + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + FunctionRule::FunctionRule( + SourceSpan&& pstate, + const EnvKey& name, + CallableSignature* arguments, + EnvRefs* idxs, + StatementVector&& children, + SilentComment* comment) : + CallableDeclaration( + std::move(pstate), + name, arguments, + std::move(children), + comment, idxs) + {} + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + MixinRule::MixinRule( + SourceSpan&& pstate, + const sass::string& name, + CallableSignature* arguments, + EnvRefs* idxs, + StatementVector&& children, + SilentComment* comment) : + CallableDeclaration( + std::move(pstate), + name, arguments, + std::move(children), + comment, idxs) + {} + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + WarnRule::WarnRule( + SourceSpan&& pstate, + Expression* expression) : + Statement(std::move(pstate)), + expression_(expression) + {} + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + ErrorRule::ErrorRule( + SourceSpan&& pstate, + Expression* expression) : + Statement(std::move(pstate)), + expression_(expression) + {} + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + DebugRule::DebugRule( + SourceSpan&& pstate, + Expression* expression) : + Statement(std::move(pstate)), + expression_(expression) + {} + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + ReturnRule::ReturnRule( + SourceSpan&& pstate, + Expression* value) : + Statement(std::move(pstate)), + value_(value) + {} + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + ContentRule::ContentRule( + SourceSpan&& pstate, + CallableArguments* arguments) : + Statement(std::move(pstate)), + arguments_(arguments) + {} + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + ExtendRule::ExtendRule( + SourceSpan&& pstate, + Interpolation* selector, + bool is_optional) : + Statement(std::move(pstate)), + selector_(selector), + is_optional_(is_optional) + {} + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + LoudComment::LoudComment( + SourceSpan&& pstate, + Interpolation* text) : + Statement(std::move(pstate)), + text_(text) + {} + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + SilentComment::SilentComment( + SourceSpan&& pstate, + sass::string&& text) : + Statement(pstate), + text_(text) + {} + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + ImportRule::ImportRule( + const SourceSpan& pstate) : + Statement(pstate) + {} + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + ModRule::ModRule( + const sass::string& prev, + const sass::string& url, + WithConfig* pwconfig, + sass::vector&& config, + bool hasLocalWith) : + WithConfig(pwconfig, + std::move(config), + hasLocalWith), + prev51_(prev), + url_(url), + module32_(nullptr), + wasExposed_(false) + {} + + ModRule::ModRule( + const sass::string& prev, + const sass::string& url, + const sass::string& prefix, + WithConfig* pwconfig, + std::set&& varFilters, + std::set&& callFilters, + sass::vector&& config, + bool isShown, + bool isHidden, + bool hasWith) : + WithConfig(pwconfig, + std::move(config), + hasWith, isShown, isHidden, + std::move(varFilters), + std::move(callFilters), + prefix), + prev51_(prev), + url_(url), + module32_(nullptr), + wasExposed_(false) + {} + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + UseRule::UseRule( + const SourceSpan& pstate, + const sass::string& prev, + const sass::string& url, + Import* import, + WithConfig* pwconfig, + sass::vector&& config, + bool hasLocalWith) : + Statement(pstate), + ModRule( + prev, url, pwconfig, + std::move(config), + hasLocalWith) + {} + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + ForwardRule::ForwardRule( + const SourceSpan& pstate, + const sass::string& prev, + const sass::string& url, + Import* import, + const sass::string& prefix, + WithConfig* pwconfig, + std::set&& varFilters, + std::set&& callFilters, + sass::vector&& config, + bool isShown, + bool isHidden, + bool hasWith) : + Statement(pstate), + ModRule( + prev, url, + prefix, pwconfig, + std::move(varFilters), + std::move(callFilters), + std::move(config), + isShown, isHidden, hasWith) + {} + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + AssignRule::AssignRule( + const SourceSpan& pstate, + const EnvKey& variable, + const sass::string ns, + sass::vector vidxs, + Expression* value, + bool is_default, + bool is_global) : + Statement(pstate), + variable_(variable), + ns_(ns), + value_(value), + is_default_(is_default), + is_global_(is_global) + {} + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + +} + diff --git a/src/ast_statements.hpp b/src/ast_statements.hpp new file mode 100644 index 0000000000..a91caaf95d --- /dev/null +++ b/src/ast_statements.hpp @@ -0,0 +1,996 @@ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#ifndef SASS_AST_STATEMENTS_HPP +#define SASS_AST_STATEMENTS_HPP + +// sass.hpp must go before all system headers +// to get the __EXTENSIONS__ fix on Solaris. +#include "capi_sass.hpp" + +#include +#include "ast_nodes.hpp" +#include "ast_callable.hpp" +#include "ast_supports.hpp" +#include "ast_statements.hpp" +#include "ast_css.hpp" +#include "environment_cnt.hpp" +#include "environment_stack.hpp" +#include "file.hpp" + +namespace Sass { + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + struct WithConfigVar { + // SourceSpan where this config var was defined + SourceSpan pstate; + // Name of this config variable (without dollar sign) + sass::string name; + // The assigned value or expressions + ValueObj value33; + ExpressionObj expression44; + bool isGuarded41 = false; + bool wasAssigned = false; + bool isNull() const { + return (value33 && value33->isaNull()) || + (expression44 && expression44->isaNullExpression()); + } + }; + + + ///////////////////////////////////////////////////////////////////////// + // Helper class to manage nested `with` config for @use, @forward and + // @include meta.load-css rules. + ///////////////////////////////////////////////////////////////////////// + + class WithConfig + { + public: + + WithConfig* parent = nullptr; + + // Flag if we do RAI + bool hasConfig = false; + bool hasShowFilter = false; + bool hasHideFilter = false; + + std::set varFilters; + std::set callFilters; + + sass::string prefix; + + // Our configuration + EnvKeyMap config; + + // Value constructor + WithConfig( + WithConfig* pwconfig, + sass::vector config, + bool hasConfig = true, + bool hasShowFilter = false, + bool hasHideFilter = false, + std::set varFilters = {}, + std::set callFilters = {}, + const sass::string& prefix = ""); + + void finalize(Logger& logger); + + // Get value and mark it as used + WithConfigVar* getCfgVar(const EnvKey& name); + + }; + + ///////////////////////////////////////////////////////////////////////// + // Abstract base class for statements that contain blocks of statements. + ///////////////////////////////////////////////////////////////////////// + + class ParentStatement : public Statement, + public Vectorized, public Env + { + public: + // Value constructor + ParentStatement( + SourceSpan&& pstate, + StatementVector&& children, + EnvRefs* idxs = nullptr); + // Value constructor + ParentStatement( + const SourceSpan& pstate, + StatementVector&& children, + EnvRefs* idxs = nullptr); + // Returns whether we have a child content block + virtual bool hasContent() const override; + }; + + ///////////////////////////////////////////////////////////////////////// + // A style rule. This applies style declarations to elements + // that match a given selector. Formerly known as `Ruleset`. + ///////////////////////////////////////////////////////////////////////// + + class StyleRule final : public ParentStatement + { + + // Interpolation forming this style rule + ADD_CONSTREF(InterpolationObj, interpolation); + + public: + + // Value constructor + StyleRule(SourceSpan&& pstate, + Interpolation* interpolation, + EnvRefs* idxs = nullptr, + StatementVector&& children = {}); + + // Statement visitor to sass values entry function + Value* accept(StatementVisitor* visitor) override final { + return visitor->visitStyleRule(this); + } + void accept(StatementVisitor* visitor) override final { + return visitor->visitStyleRule(this); + } + + // Implement final up-casting method + IMPLEMENT_ISA_CASTER(StyleRule); + }; + + ///////////////////////////////////////////////////////////////////////// + // Declarations -- style rules consisting of a property name and values. + ///////////////////////////////////////////////////////////////////////// + + class Declaration final : public ParentStatement + { + + ADD_CONSTREF(InterpolationObj, name); + ADD_CONSTREF(ExpressionObj, value); + ADD_CONSTREF(bool, is_custom_property); + + public: + + // Value constructor + Declaration(SourceSpan&& pstate, + Interpolation* name, + Expression* value = nullptr, + bool is_custom_property = false, + StatementVector&& children = {}); + + // Statement visitor to sass values entry function + Value* accept(StatementVisitor* visitor) override final { + return visitor->visitDeclaration(this); + } + void accept(StatementVisitor* visitor) override final { + return visitor->visitDeclaration(this); + } + + bool isCustomProperty() const; + + }; + + ///////////////////////////////////////////////////////////////////////// + // The Sass `@for` control directive. + ///////////////////////////////////////////////////////////////////////// + + class ForRule final : public ParentStatement + { + + // Name of loop variable + ADD_CONSTREF(EnvKey, varname); + // Lower boundary to iterator from + ADD_CONSTREF(ExpressionObj, lower_bound); + // Upper boundary to iterator to + ADD_CONSTREF(ExpressionObj, upper_bound); + // Is upper boundary inclusive? + ADD_CONSTREF(bool, is_inclusive); + + public: + + // Value constructor + ForRule( + SourceSpan&& pstate, + const EnvKey& varname, + Expression* lower_bound, + Expression* upper_bound, + bool is_inclusive = false, + EnvRefs* idxs = nullptr, + StatementVector&& children = {}); + + // Statement visitor to sass values entry function + Value* accept(StatementVisitor* visitor) override final { + return visitor->visitForRule(this); + } + void accept(StatementVisitor* visitor) override final { + return visitor->visitForRule(this); + } + + }; + + ///////////////////////////////////////////////////////////////////////// + // The Sass `@each` control directive. + ///////////////////////////////////////////////////////////////////////// + + class EachRule final : public ParentStatement + { + + // Names of loop variables for key and value + ADD_CONSTREF(EnvKeys, variables); + + // Expression object to loop over + ADD_CONSTREF(ExpressionObj, expressions); + + public: + + // Value constructor + EachRule( + SourceSpan&& pstate, + const EnvKeys& variables, + Expression* expressions, + EnvRefs* idxs = nullptr, + StatementVector&& children = {}); + + // Statement visitor to sass values entry function + Value* accept(StatementVisitor* visitor) override final { + return visitor->visitEachRule(this); + } + void accept(StatementVisitor* visitor) override final { + return visitor->visitEachRule(this); + } + + }; + + ///////////////////////////////////////////////////////////////////////// + // The Sass `@while` control directive. + ///////////////////////////////////////////////////////////////////////// + + class WhileRule final : public ParentStatement + { + + // Condition to evaluate every loop + ADD_CONSTREF(ExpressionObj, condition); + + public: + + // Value constructor + WhileRule( + SourceSpan&& pstate, + Expression* condition, + EnvRefs* idxs = nullptr, + StatementVector&& children = {}); + + // Statement visitor to sass values entry function + Value* accept(StatementVisitor* visitor) override final { + return visitor->visitWhileRule(this); + } + void accept(StatementVisitor* visitor) override final { + return visitor->visitWhileRule(this); + } + + }; + + ///////////////////////////////////////////////////////////////////////// + // The query that determines on which platforms the styles will be in + // effect. This is only parsed after the interpolation has been resolved. + ///////////////////////////////////////////////////////////////////////// + + class MediaRule final : public ParentStatement + { + + // Interpolation forming this media rule + ADD_CONSTREF(InterpolationObj, query) + + public: + + // Value constructor + MediaRule( + SourceSpan&& pstate, + Interpolation* query, + EnvRefs* idxs, + StatementVector&& children = {}); + + // Statement visitor to sass values entry function + Value* accept(StatementVisitor* visitor) override final { + return visitor->visitMediaRule(this); + } + void accept(StatementVisitor* visitor) override final { + return visitor->visitMediaRule(this); + } + + }; + + ///////////////////////////////////////////////////////////////////////// + // At-rules -- arbitrary directives beginning with "@" + // The rules may have more optional statement blocks. + ///////////////////////////////////////////////////////////////////////// + + class AtRule final : public ParentStatement + { + + // Interpolation naming this at-rule + ADD_CONSTREF(InterpolationObj, name); + // Interpolations with additional options + ADD_CONSTREF(InterpolationObj, value); + // Flag if the at-rule was parsed childless + ADD_CONSTREF(bool, isChildless); + + public: + + // Value constructor + AtRule( + SourceSpan&& pstate, + Interpolation* name, + Interpolation* value, + EnvRefs* idxs, + bool is_childless = true, + StatementVector&& children = {}); + + // Statement visitor to sass values entry function + Value* accept(StatementVisitor* visitor) override final { + return visitor->visitAtRule(this); + } + void accept(StatementVisitor* visitor) override final { + return visitor->visitAtRule(this); + } + + }; + + ///////////////////////////////////////////////////////////////////////// + // An `@at-root` rule + ///////////////////////////////////////////////////////////////////////// + + class AtRootRule final : public ParentStatement + { + + // Interpolation forming this at-root rule + ADD_CONSTREF(InterpolationObj, query); + + public: + + // Value constructor + AtRootRule( + SourceSpan&& pstate, + Interpolation* query = nullptr, + EnvRefs* idxs = nullptr, + StatementVector&& children = {}); + + // Statement visitor to sass values entry function + Value* accept(StatementVisitor* visitor) override final { + return visitor->visitAtRootRule(this); + } + void accept(StatementVisitor* visitor) override final { + return visitor->visitAtRootRule(this); + } + + }; + + ///////////////////////////////////////////////////////////////////////// + // The Sass `@if` control directive. + ///////////////////////////////////////////////////////////////////////// + + class IfRule final : public ParentStatement + { + // Predicate is optional, which indicates an else block. + // In this case further `alternatives` are simply ignored. + ADD_CONSTREF(ExpressionObj, predicate); + // The else or else-if block + ADD_CONSTREF(IfRuleObj, alternative); + + public: + + // Value constructor + IfRule(SourceSpan&& pstate, + EnvRefs* idxs, + StatementVector&& children = {}, + Expression* predicate = nullptr, + IfRule* alternative = {}); + + // Also check alternative for content block + bool hasContent() const override final; + + // Statement visitor to sass values entry function + Value* accept(StatementVisitor* visitor) override final { + return visitor->visitIfRule(this); + } + void accept(StatementVisitor* visitor) override final { + return visitor->visitIfRule(this); + } + + }; + + ///////////////////////////////////////////////////////////////////////// + // `@supports` rule. + ///////////////////////////////////////////////////////////////////////// + + class SupportsRule final : public ParentStatement + { + + // The condition forming this supports rule + ADD_CONSTREF(SupportsConditionObj, condition); + + public: + + SupportsRule( + SourceSpan&& pstate, + SupportsCondition* condition, + EnvRefs* idxs = nullptr, + StatementVector&& children = {}); + + // Statement visitor to sass values entry function + Value* accept(StatementVisitor* visitor) override final { + return visitor->visitSupportsRule(this); + } + void accept(StatementVisitor* visitor) override final { + return visitor->visitSupportsRule(this); + } + + }; + + ///////////////////////////////////////////////////////////////////////// + // Base class for specialized callable declarations. + ///////////////////////////////////////////////////////////////////////// + + class CallableDeclaration : public ParentStatement + { + + // The name of this callable. + ADD_CONSTREF(EnvKey, name); + // The comment immediately preceding this declaration. + ADD_CONSTREF(SilentCommentObj, comment); + // The declared arguments this callable accepts. + ADD_CONSTREF(CallableSignatureObj, arguments); + + public: + + // Value constructor + CallableDeclaration( + SourceSpan&& pstate, + const EnvKey& name, + CallableSignature* arguments, + StatementVector&& children = {}, + SilentComment* comment = nullptr, + EnvRefs* idxs = nullptr); + + // Value constructor + CallableDeclaration( + const SourceSpan& pstate, + const EnvKey& name, + CallableSignature* arguments, + StatementVector&& children = {}, + SilentComment* comment = nullptr, + EnvRefs* idxs = nullptr); + + // Equality comparator (needed for `get-function` value) + bool operator==(const CallableDeclaration& rhs) const; + + // Declare up-casting methods + DECLARE_ISA_CASTER(MixinRule); + }; + + ///////////////////////////////////////////////////////////////////////// + // ToDo: not exactly sure anymore + ///////////////////////////////////////////////////////////////////////// + + class ContentBlock final : + public CallableDeclaration + { + + // Content function reference + ADD_CONSTREF(EnvRef, cidx); + + public: + + // Value constructor + ContentBlock( + SourceSpan&& pstate, + CallableSignature* arguments = nullptr, + EnvRefs* idxs = nullptr, + StatementVector&& children = {}, + SilentComment* comment = nullptr); + + // Statement visitor to sass values entry function + Value* accept(StatementVisitor* visitor) override final { + return visitor->visitContentBlock(this); + } + void accept(StatementVisitor* visitor) override final { + return visitor->visitContentBlock(this); + } + + }; + + ///////////////////////////////////////////////////////////////////////// + // Specialization for `@function` callables + ///////////////////////////////////////////////////////////////////////// + + class FunctionRule final : + public CallableDeclaration + { + + // Function reference + ADD_CONSTREF(EnvRef, fidx); + + public: + + // Value constructor + FunctionRule( + SourceSpan&& pstate, + const EnvKey& name, + CallableSignature* arguments, + EnvRefs* idxs = nullptr, + StatementVector&& children = {}, + SilentComment* comment = nullptr); + + // Statement visitor to sass values entry function + Value* accept(StatementVisitor* visitor) override final { + return visitor->visitFunctionRule(this); + } + void accept(StatementVisitor* visitor) override final { + return visitor->visitFunctionRule(this); + } + + }; + + ///////////////////////////////////////////////////////////////////////// + // Specialization for `@mixin` callables + ///////////////////////////////////////////////////////////////////////// + + class MixinRule final : + public CallableDeclaration + { + + // Mixin function reference + ADD_CONSTREF(EnvRef, midx); + + public: + + // Value constructor + MixinRule( + SourceSpan&& pstate, + const sass::string& name, + CallableSignature* arguments, + EnvRefs* idxs = nullptr, + StatementVector&& children = {}, + SilentComment* comment = nullptr); + + // Statement visitor to sass values entry function + Value* accept(StatementVisitor* visitor) override final { + return visitor->visitMixinRule(this); + } + void accept(StatementVisitor* visitor) override final { + return visitor->visitMixinRule(this); + } + + // Implement final up-casting method + IMPLEMENT_ISA_CASTER(MixinRule); + }; + + ///////////////////////////////////////////////////////////////////////// + // The Sass `@warn` directive. + ///////////////////////////////////////////////////////////////////////// + + class WarnRule final : public Statement + { + + // Expression forming this warning rule + ADD_CONSTREF(ExpressionObj, expression); + + public: + + // Value constructor + WarnRule(SourceSpan&& pstate, + Expression* expression); + + // Statement visitor to sass values entry function + Value* accept(StatementVisitor* visitor) override final { + return visitor->visitWarnRule(this); + } + void accept(StatementVisitor* visitor) override final { + return visitor->visitWarnRule(this); + } + + }; + + ///////////////////////////////////////////////////////////////////////// + // The Sass `@error` directive. + ///////////////////////////////////////////////////////////////////////// + class ErrorRule final : public Statement + { + + // Expression forming this error rule + ADD_CONSTREF(ExpressionObj, expression); + + public: + + // Value constructor + ErrorRule(SourceSpan&& pstate, + Expression* expression); + + // Statement visitor to sass values entry function + Value* accept(StatementVisitor* visitor) override final { + return visitor->visitErrorRule(this); + } + void accept(StatementVisitor* visitor) override final { + return visitor->visitErrorRule(this); + } + + }; + + ///////////////////////////////////////////////////////////////////////// + // The Sass `@debug` directive. + ///////////////////////////////////////////////////////////////////////// + + class DebugRule final : public Statement + { + + // Expression forming this debug rule + ADD_CONSTREF(ExpressionObj, expression); + + public: + + // Value constructor + DebugRule(SourceSpan&& pstate, + Expression* expression); + + // Statement visitor to sass values entry function + Value* accept(StatementVisitor* visitor) override final { + return visitor->visitDebugRule(this); + } + void accept(StatementVisitor* visitor) override final { + return visitor->visitDebugRule(this); + } + + }; + + ///////////////////////////////////////////////////////////////////////// + // The @return directive for use inside SassScript functions. + ///////////////////////////////////////////////////////////////////////// + + class ReturnRule final : public Statement + { + + // Expression forming this return rule + ADD_CONSTREF(ExpressionObj, value); + + public: + + // Value constructor + ReturnRule(SourceSpan&& pstate, + Expression* value = nullptr); + + // Statement visitor to sass values entry function + Value* accept(StatementVisitor* visitor) override final { + return visitor->visitReturnRule(this); + } + void accept(StatementVisitor* visitor) override final { + return visitor->visitReturnRule(this); + } + + }; + + ///////////////////////////////////////////////////////////////////////// + // The @content directive for mixin content blocks. + ///////////////////////////////////////////////////////////////////////// + + class ContentRule final : public Statement + { + + // Expression forming this content rule + ADD_CONSTREF(CallableArgumentsObj, arguments); + + public: + + // Value constructor + ContentRule(SourceSpan&& pstate, + CallableArguments* arguments); + + // Specialize method to indicate we have content + bool hasContent() const override final { return true; } + + // Statement visitor to sass values entry function + Value* accept(StatementVisitor* visitor) override final { + return visitor->visitContentRule(this); + } + void accept(StatementVisitor* visitor) override final { + return visitor->visitContentRule(this); + } + + }; + + ///////////////////////////////////////////////////////////////////////// + // The Sass `@extend` directive. + ///////////////////////////////////////////////////////////////////////// + + class ExtendRule final : public Statement + { + + // This should be a simple selector only! + // ToDo: change when we remove old code! + ADD_CONSTREF(InterpolationObj, selector); + // Flag if extend had optional flag + ADD_CONSTREF(bool, is_optional); + + public: + + // Value constructor + ExtendRule( + SourceSpan&& pstate, + Interpolation* selector, + bool is_optional = false); + + // Statement visitor to sass values entry function + Value* accept(StatementVisitor* visitor) override final { + return visitor->visitExtendRule(this); + } + void accept(StatementVisitor* visitor) override final { + return visitor->visitExtendRule(this); + } + + }; + + ///////////////////////////////////////////////////////////////////////// + // CSS comments. These may be interpolated. + ///////////////////////////////////////////////////////////////////////// + + class LoudComment final : public Statement + { + // The interpolated text of this comment, including comment characters. + ADD_CONSTREF(InterpolationObj, text) + + public: + + // Value constructor + LoudComment( + SourceSpan&& pstate, + Interpolation* text); + + // Statement visitor to sass values entry function + Value* accept(StatementVisitor* visitor) override final { + return visitor->visitLoudComment(this); + } + void accept(StatementVisitor* visitor) override final { + return visitor->visitLoudComment(this); + } + + }; + + ///////////////////////////////////////////////////////////////////////// + // Silent comment, starting with `//`. + ///////////////////////////////////////////////////////////////////////// + + class SilentComment final : public Statement + { + + // The text of this comment, including comment characters. + ADD_CONSTREF(sass::string, text) + + public: + + // Value constructor + SilentComment( + SourceSpan&& pstate, + sass::string&& text); + + // Statement visitor to sass values entry function + Value* accept(StatementVisitor* visitor) override final { + return visitor->visitSilentComment(this); + } + void accept(StatementVisitor* visitor) override final { + return visitor->visitSilentComment(this); + } + + }; + + ///////////////////////////////////////////////////////////////////////// + // `@import` rule. + ///////////////////////////////////////////////////////////////////////// + + class ImportRule final : public Statement, + public Vectorized + { + + public: + + // Value constructor + ImportRule(const SourceSpan& pstate); + + // Statement visitor to sass values entry function + Value* accept(StatementVisitor* visitor) override final { + return visitor->visitImportRule(this); + } + void accept(StatementVisitor* visitor) override final { + return visitor->visitImportRule(this); + } + + }; + + ///////////////////////////////////////////////////////////////////////// + // Base class for @use and @forward rules + ///////////////////////////////////////////////////////////////////////// + + class ModRule : public WithConfig + { + private: + + ADD_CONSTREF(sass::string, prev51); + ADD_CONSTREF(sass::string, url); + ADD_CONSTREF(sass::string, ns); + + // The associated module (required) + ADD_PROPERTY(Module*, module32); + + // Optional root object, indicating that we have + // a connected environment. Must be the same instance + // as root if set (as Root implements Module). + ADD_CONSTREF(RootObj, root47); + + // Flag to see if rule was already exposed + // Normally modules are exposed as singletons + ADD_CONSTREF(bool, wasExposed); + + public: + + ModRule( + const sass::string& prev, + const sass::string& url, + WithConfig* pwconfig = nullptr, + sass::vector&& config = {}, + bool hasLocalWith = false); + + ModRule( + const sass::string& prev, + const sass::string& url, + const sass::string& prefix, + WithConfig* pwconfig, + std::set&& varFilters, + std::set&& callFilters, + sass::vector&& config, + bool isShown, + bool isHidden, + bool hasWith); + }; + + + ///////////////////////////////////////////////////////////////////////// + // `@use` rule. + ///////////////////////////////////////////////////////////////////////// + + class UseRule final : public Statement, public ModRule + { + public: + + // Value constructor + UseRule( + const SourceSpan& pstate, + const sass::string& prev, + const sass::string& url, + Import* import, + WithConfig* pwconfig, + sass::vector&& config, + bool hasLocalWith); + + // Statement visitor to sass values entry function + Value* accept(StatementVisitor* visitor) override final { + return visitor->visitUseRule(this); + } + void accept(StatementVisitor* visitor) override final { + return visitor->visitUseRule(this); + } + }; + + ///////////////////////////////////////////////////////////////////////// + // `@forward` rule. + ///////////////////////////////////////////////////////////////////////// + + class ForwardRule final : public Statement, public ModRule + { + public: + + // Value constructor + ForwardRule( + const SourceSpan& pstate, + const sass::string& prev, + const sass::string& url, + Import* import, + const sass::string& prefix, + WithConfig* pwconfig, + std::set&& varFilters, + std::set&& callFilters, + sass::vector&& config, + bool isShown, bool isHidden, bool hasWith); + + // Statement visitor to sass values entry function + Value* accept(StatementVisitor* visitor) override final { + return visitor->visitForwardRule(this); + } + void accept(StatementVisitor* visitor) override final { + return visitor->visitForwardRule(this); + } + + }; + + ///////////////////////////////////////////////////////////////////////// + // Assignment rule to assign (evaluated) expression to variable + ///////////////////////////////////////////////////////////////////////// + + class AssignRule final : public Statement + { + + private: + + ADD_CONSTREF(EnvRef, vidx); + ADD_CONSTREF(EnvKey, variable); + ADD_CONSTREF(sass::string, ns); + ADD_CONSTREF(ExpressionObj, value); + ADD_CONSTREF(bool, is_default); // ToDO rename + ADD_CONSTREF(bool, is_global); // ToDO rename + + public: + // Value constructor + AssignRule( + const SourceSpan& pstate, + const EnvKey& variable, + const sass::string ns, + sass::vector vidxs, + Expression* value, + bool is_default = false, + bool is_global = false); + // Statement visitor to sass values entry function + Value* accept(StatementVisitor* visitor) override final { + return visitor->visitAssignRule(this); + } + void accept(StatementVisitor* visitor) override final { + return visitor->visitAssignRule(this); + } + }; + + ///////////////////////////////////////////////////////////////////////// + // The @include mixin invocation rule + ///////////////////////////////////////////////////////////////////////// + + class IncludeRule final : public Statement + { + + // The arguments passed to the callable. + ADD_CONSTREF(CallableArgumentsObj, arguments); + + private: + + // The namespace of the mixin being invoked, or + // `null` if it's invoked without a namespace. + ADD_CONSTREF(sass::string, ns); + + // The name of the mixin being invoked. + ADD_CONSTREF(EnvKey, name); + + ADD_CONSTREF(EnvRef, midx); + + // The block that will be invoked for [ContentRule]s in the mixin + // being invoked, or `null` if this doesn't pass a content block. + ADD_CONSTREF(ContentBlockObj, content); + + public: + + // Value constructor + IncludeRule( + SourceSpan&& pstate, + const EnvKey& name, + const sass::string& ns, + CallableArguments* arguments, + ContentBlock* content = nullptr); + + // Check if we have a valid content block + bool hasContent() const override final; + + // Statement visitor to sass values entry function + Value* accept(StatementVisitor* visitor) override final { + return visitor->visitIncludeRule(this); + } + void accept(StatementVisitor* visitor) override final { + return visitor->visitIncludeRule(this); + } + }; + +} + +#include "modules.hpp" +#include "stylesheet.hpp" + +#endif diff --git a/src/ast_supports.cpp b/src/ast_supports.cpp index 9cd0bf3684..f9f91f44bb 100644 --- a/src/ast_supports.cpp +++ b/src/ast_supports.cpp @@ -1,112 +1,103 @@ -// sass.hpp must go before all system headers to get the -// __EXTENSIONS__ fix on Solaris. -#include "sass.hpp" -#include "ast.hpp" - +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#include "ast_supports.hpp" namespace Sass { ///////////////////////////////////////////////////////////////////////// + // The abstract superclass of all Supports conditions. ///////////////////////////////////////////////////////////////////////// - SupportsRule::SupportsRule(SourceSpan pstate, SupportsConditionObj condition, Block_Obj block) - : ParentStatement(pstate, block), condition_(condition) - { statement_type(SUPPORTS); } - SupportsRule::SupportsRule(const SupportsRule* ptr) - : ParentStatement(ptr), condition_(ptr->condition_) - { statement_type(SUPPORTS); } - bool SupportsRule::bubbles() { return true; } + SupportsCondition::SupportsCondition( + const SourceSpan& pstate) : + AstNode(pstate) + {} ///////////////////////////////////////////////////////////////////////// + // An operator condition (e.g. `CONDITION1 and CONDITION2`). ///////////////////////////////////////////////////////////////////////// - SupportsCondition::SupportsCondition(SourceSpan pstate) - : Expression(pstate) - { } - - SupportsCondition::SupportsCondition(const SupportsCondition* ptr) - : Expression(ptr) - { } + SupportsOperation::SupportsOperation( + const SourceSpan& pstate, + SupportsCondition* lhs, + SupportsCondition* rhs, + Operand operand) : + SupportsCondition(pstate), + left_(lhs), + right_(rhs), + operand_(operand) + {} ///////////////////////////////////////////////////////////////////////// + // A supports function ///////////////////////////////////////////////////////////////////////// - SupportsOperation::SupportsOperation(SourceSpan pstate, SupportsConditionObj l, SupportsConditionObj r, Operand o) - : SupportsCondition(pstate), left_(l), right_(r), operand_(o) - { } - SupportsOperation::SupportsOperation(const SupportsOperation* ptr) - : SupportsCondition(ptr), - left_(ptr->left_), - right_(ptr->right_), - operand_(ptr->operand_) - { } - - bool SupportsOperation::needs_parens(SupportsConditionObj cond) const - { - if (SupportsOperationObj op = Cast(cond)) { - return op->operand() != operand(); - } - return Cast(cond) != NULL; - } + SupportsFunction::SupportsFunction( + const SourceSpan& pstate, + Interpolation* name, + Interpolation* args) : + SupportsCondition(pstate), + name_(name), + args_(args) + {} ///////////////////////////////////////////////////////////////////////// + // A supports anything ///////////////////////////////////////////////////////////////////////// - SupportsNegation::SupportsNegation(SourceSpan pstate, SupportsConditionObj c) - : SupportsCondition(pstate), condition_(c) - { } - SupportsNegation::SupportsNegation(const SupportsNegation* ptr) - : SupportsCondition(ptr), condition_(ptr->condition_) - { } - - bool SupportsNegation::needs_parens(SupportsConditionObj cond) const - { - return Cast(cond) || - Cast(cond); - } + SupportsAnything::SupportsAnything( + const SourceSpan& pstate, + Interpolation* contents) : + SupportsCondition(pstate), + contents_(contents) + {} ///////////////////////////////////////////////////////////////////////// + // A negation condition (`not CONDITION`). ///////////////////////////////////////////////////////////////////////// - SupportsDeclaration::SupportsDeclaration(SourceSpan pstate, ExpressionObj f, ExpressionObj v) - : SupportsCondition(pstate), feature_(f), value_(v) - { } - SupportsDeclaration::SupportsDeclaration(const SupportsDeclaration* ptr) - : SupportsCondition(ptr), - feature_(ptr->feature_), - value_(ptr->value_) - { } - - bool SupportsDeclaration::needs_parens(SupportsConditionObj cond) const - { - return false; - } + SupportsNegation::SupportsNegation( + const SourceSpan& pstate, + SupportsCondition* condition) : + SupportsCondition(pstate), + condition_(condition) + {} ///////////////////////////////////////////////////////////////////////// + // A declaration condition (e.g. `(feature: value)`). ///////////////////////////////////////////////////////////////////////// - Supports_Interpolation::Supports_Interpolation(SourceSpan pstate, ExpressionObj v) - : SupportsCondition(pstate), value_(v) - { } - Supports_Interpolation::Supports_Interpolation(const Supports_Interpolation* ptr) - : SupportsCondition(ptr), - value_(ptr->value_) - { } + SupportsDeclaration::SupportsDeclaration( + const SourceSpan& pstate, + Expression* feature, + Expression* value) + : SupportsCondition(pstate), + feature_(feature), + value_(value) + {} - bool Supports_Interpolation::needs_parens(SupportsConditionObj cond) const + bool SupportsDeclaration::isCustomProperty() const { + if (const auto& exp = feature_->isaStringExpression()) { + if (exp->hasQuotes() == false) { + const auto& text = exp->text()->getInitialPlain(); + return StringUtils::startsWith(text, "--", 2); + } + } return false; } ///////////////////////////////////////////////////////////////////////// + // An interpolation condition (e.g. `#{$var}`). ///////////////////////////////////////////////////////////////////////// - IMPLEMENT_AST_OPERATORS(SupportsRule); - IMPLEMENT_AST_OPERATORS(SupportsCondition); - IMPLEMENT_AST_OPERATORS(SupportsOperation); - IMPLEMENT_AST_OPERATORS(SupportsNegation); - IMPLEMENT_AST_OPERATORS(SupportsDeclaration); - IMPLEMENT_AST_OPERATORS(Supports_Interpolation); + SupportsInterpolation::SupportsInterpolation( + const SourceSpan& pstate, + Expression* value) : + SupportsCondition(pstate), + value_(value) + {} ///////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////// diff --git a/src/ast_supports.hpp b/src/ast_supports.hpp index 8b083ebdf4..54135ae727 100644 --- a/src/ast_supports.hpp +++ b/src/ast_supports.hpp @@ -1,121 +1,182 @@ -#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" +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#ifndef SASS_AST_SUPPORTS_HPP +#define SASS_AST_SUPPORTS_HPP -namespace Sass { +// sass.hpp must go before all system headers +// to get the __EXTENSIONS__ fix on Solaris. +#include "capi_sass.hpp" - //////////////////// - // `@supports` rule. - //////////////////// - class SupportsRule : public ParentStatement { - ADD_PROPERTY(SupportsConditionObj, condition) - public: - SupportsRule(SourceSpan pstate, SupportsConditionObj condition, Block_Obj block = {}); - bool bubbles() override; - ATTACH_AST_OPERATIONS(SupportsRule) - ATTACH_CRTP_PERFORM_METHODS() - }; +#include "ast_expressions.hpp" - ////////////////////////////////////////////////////// +namespace Sass { + + ///////////////////////////////////////////////////////////////////////// // The abstract superclass of all Supports conditions. - ////////////////////////////////////////////////////// - class SupportsCondition : public Expression { + ///////////////////////////////////////////////////////////////////////// + + class SupportsCondition : public AstNode + { public: - SupportsCondition(SourceSpan pstate); - virtual bool needs_parens(SupportsConditionObj cond) const { return false; } - ATTACH_AST_OPERATIONS(SupportsCondition) - ATTACH_CRTP_PERFORM_METHODS() + + // Value constructor + SupportsCondition( + const SourceSpan& pstate); + + // Declare up-casting methods + DECLARE_ISA_CASTER(SupportsOperation); + DECLARE_ISA_CASTER(SupportsFunction); + DECLARE_ISA_CASTER(SupportsAnything); + DECLARE_ISA_CASTER(SupportsNegation); + DECLARE_ISA_CASTER(SupportsDeclaration); + DECLARE_ISA_CASTER(SupportsInterpolation); }; - //////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// // An operator condition (e.g. `CONDITION1 and CONDITION2`). - //////////////////////////////////////////////////////////// - class SupportsOperation : public SupportsCondition { + ///////////////////////////////////////////////////////////////////////// + + class SupportsOperation final : public SupportsCondition + { public: + enum Operand { AND, OR }; + + private: + + ADD_CONSTREF(SupportsConditionObj, left); + ADD_CONSTREF(SupportsConditionObj, right); + ADD_CONSTREF(Operand, operand); + + public: + + // Value constructor + SupportsOperation( + const SourceSpan& pstate, + SupportsCondition* lhs, + SupportsCondition* rhs, + Operand operand); + + // Implement final up-casting method + IMPLEMENT_ISA_CASTER(SupportsOperation); + }; + + ///////////////////////////////////////////////////////////////////////// + // A supports function + ///////////////////////////////////////////////////////////////////////// + + class SupportsFunction final : public SupportsCondition + { private: - ADD_PROPERTY(SupportsConditionObj, left); - ADD_PROPERTY(SupportsConditionObj, right); - ADD_PROPERTY(Operand, operand); + + ADD_CONSTREF(InterpolationObj, name); + ADD_CONSTREF(InterpolationObj, args); + public: - SupportsOperation(SourceSpan pstate, SupportsConditionObj l, SupportsConditionObj r, Operand o); - virtual bool needs_parens(SupportsConditionObj cond) const override; - ATTACH_AST_OPERATIONS(SupportsOperation) - ATTACH_CRTP_PERFORM_METHODS() + + // Value constructor + SupportsFunction( + const SourceSpan& pstate, + Interpolation* name, + Interpolation* args); + + // Implement final up-casting method + IMPLEMENT_ISA_CASTER(SupportsFunction); }; - ////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + // A supports anything condition + ///////////////////////////////////////////////////////////////////////// + + class SupportsAnything final : public SupportsCondition + { + + private: + + ADD_CONSTREF(InterpolationObj, contents); + + public: + + // Value constructor + SupportsAnything( + const SourceSpan& pstate, + Interpolation* contents); + + // Implement final up-casting method + IMPLEMENT_ISA_CASTER(SupportsAnything); + }; + + ///////////////////////////////////////////////////////////////////////// // A negation condition (`not CONDITION`). - ////////////////////////////////////////// - class SupportsNegation : public SupportsCondition { + ///////////////////////////////////////////////////////////////////////// + + class SupportsNegation final : public SupportsCondition + { + private: - ADD_PROPERTY(SupportsConditionObj, condition); + + ADD_CONSTREF(SupportsConditionObj, condition); + public: - SupportsNegation(SourceSpan pstate, SupportsConditionObj c); - virtual bool needs_parens(SupportsConditionObj cond) const override; - ATTACH_AST_OPERATIONS(SupportsNegation) - ATTACH_CRTP_PERFORM_METHODS() + + // Value constructor + SupportsNegation( + const SourceSpan& pstate, + SupportsCondition* condition); + + // Implement final up-casting method + IMPLEMENT_ISA_CASTER(SupportsNegation); }; - ///////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// // A declaration condition (e.g. `(feature: value)`). - ///////////////////////////////////////////////////// - class SupportsDeclaration : public SupportsCondition { + ///////////////////////////////////////////////////////////////////////// + class SupportsDeclaration final : public SupportsCondition + { private: - ADD_PROPERTY(ExpressionObj, feature); - ADD_PROPERTY(ExpressionObj, value); + + ADD_CONSTREF(ExpressionObj, feature); + ADD_CONSTREF(ExpressionObj, value); + public: - SupportsDeclaration(SourceSpan pstate, ExpressionObj f, ExpressionObj v); - virtual bool needs_parens(SupportsConditionObj cond) const override; - ATTACH_AST_OPERATIONS(SupportsDeclaration) - ATTACH_CRTP_PERFORM_METHODS() + + // Value constructor + SupportsDeclaration( + const SourceSpan& pstate, + Expression* feature, + Expression* value); + + bool isCustomProperty() const; + + // Implement final up-casting method + IMPLEMENT_ISA_CASTER(SupportsDeclaration); }; - /////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// // An interpolation condition (e.g. `#{$var}`). - /////////////////////////////////////////////// - class Supports_Interpolation : public SupportsCondition { + ///////////////////////////////////////////////////////////////////////// + + class SupportsInterpolation final : public SupportsCondition + { private: - ADD_PROPERTY(ExpressionObj, value); + + ADD_CONSTREF(ExpressionObj, value); + public: - Supports_Interpolation(SourceSpan pstate, ExpressionObj v); - virtual bool needs_parens(SupportsConditionObj cond) const override; - ATTACH_AST_OPERATIONS(Supports_Interpolation) - ATTACH_CRTP_PERFORM_METHODS() + + // Value constructor + SupportsInterpolation( + const SourceSpan& pstate, + Expression* value); + + // Implement final up-casting method + IMPLEMENT_ISA_CASTER(SupportsInterpolation); }; + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + } #endif diff --git a/src/ast_values.cpp b/src/ast_values.cpp index fccb0967a7..56ddafb017 100644 --- a/src/ast_values.cpp +++ b/src/ast_values.cpp @@ -1,881 +1,1396 @@ -// sass.hpp must go before all system headers to get the -// __EXTENSIONS__ fix on Solaris. -#include "sass.hpp" -#include "ast.hpp" +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#include "ast_values.hpp" -namespace Sass { - - void str_rtrim(sass::string& str, const sass::string& delimiters = " \f\n\r\t\v") - { - str.erase( str.find_last_not_of( delimiters ) + 1 ); - } +#include "logger.hpp" +#include "fn_utils.hpp" +#include "exceptions.hpp" +#include "dart_helpers.hpp" +#include "ast_nodes.hpp" +#include "unicode.hpp" - ///////////////////////////////////////////////////////////////////////// - ///////////////////////////////////////////////////////////////////////// +#include - PreValue::PreValue(SourceSpan pstate, bool d, bool e, bool i, Type ct) - : Expression(pstate, d, e, i, ct) - { } - PreValue::PreValue(const PreValue* ptr) - : Expression(ptr) - { } +namespace Sass { ///////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////// - Value::Value(SourceSpan pstate, bool d, bool e, bool i, Type ct) - : PreValue(pstate, d, e, i, ct) - { } - Value::Value(const Value* ptr) - : PreValue(ptr) - { } + // This should be thread-safe + static std::hash boolHasher; + static std::hash doubleHasher; + static std::hash sizetHasher; + static std::hash stringHasher; + + const double NaN = std::numeric_limits::quiet_NaN(); ///////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////// - List::List(SourceSpan pstate, size_t size, enum Sass_Separator sep, bool argl, bool bracket) - : Value(pstate), - Vectorized(size), - separator_(sep), - is_arglist_(argl), - is_bracketed_(bracket), - from_selector_(false) - { concrete_type(LIST); } + CustomError::CustomError( + const SourceSpan& pstate, + const sass::string& msg) : + Value(pstate), + message_(msg) + {} - 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); } + CustomError::CustomError(const CustomError* ptr) + : Value(ptr), message_(ptr->message_) + {} - size_t List::hash() const + ///////////////////////////////////////////////////////////////////////// + + bool CustomError::operator== (const Value& rhs) const { - if (hash_ == 0) { - hash_ = std::hash()(sep_string()); - hash_combine(hash_, std::hash()(is_bracketed())); - for (size_t i = 0, L = length(); i < L; ++i) - hash_combine(hash_, (elements()[i])->hash()); + if (auto right = rhs.isaCustomError()) { + return *this == *right; } - return hash_; + return false; } - void List::set_delayed(bool delayed) + bool CustomError::operator== (const CustomError& rhs) const { - is_delayed(delayed); - // don't set children + return message() == rhs.message(); } - bool List::operator< (const Expression& rhs) const - { - if (auto r = Cast(&rhs)) { - if (length() < r->length()) return true; - if (length() > r->length()) return false; - const auto& left = elements(); - const auto& right = r->elements(); - for (size_t i = 0; i < left.size(); i += 1) { - if (*left[i] < *right[i]) return true; - if (*left[i] == *right[i]) continue; - return false; - } - return false; - } - // compare/sort by type - return type() < rhs.type(); + ///////////////////////////////////////////////////////////////////////// + + void CustomError::accept(ValueVisitor* visitor) { + throw std::runtime_error("CustomError::accept not implemented"); + } + Value* CustomError::accept(ValueVisitor* visitor) { + throw std::runtime_error("CustomError::accept not implemented"); } - bool List::operator== (const Expression& rhs) const + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + CustomWarning::CustomWarning( + const SourceSpan& pstate, + const sass::string& msg) : + Value(pstate), + message_(msg) + {} + + CustomWarning::CustomWarning(const CustomWarning* ptr) + : Value(ptr), message_(ptr->message_) + {} + + ///////////////////////////////////////////////////////////////////////// + + bool CustomWarning::operator== (const Value& rhs) const { - if (auto r = Cast(&rhs)) { - if (length() != r->length()) return false; - if (separator() != r->separator()) return false; - if (is_bracketed() != r->is_bracketed()) return false; - for (size_t i = 0, L = length(); i < L; ++i) { - auto rv = r->at(i); - auto lv = this->at(i); - if (!lv && rv) return false; - else if (!rv && lv) return false; - else if (*lv != *rv) return false; - } - return true; + if (auto right = rhs.isaCustomWarning()) { + return *this == *right; } return false; } - size_t List::size() const { - if (!is_arglist_) return length(); - // arglist expects a list of arguments - // so we need to break before keywords - for (size_t i = 0, L = length(); i < L; ++i) { - ExpressionObj obj = this->at(i); - if (Argument* arg = Cast(obj)) { - if (!arg->name().empty()) return i; - } - } - return length(); + bool CustomWarning::operator== (const CustomWarning& rhs) const + { + return message() == rhs.message(); } + ///////////////////////////////////////////////////////////////////////// - ExpressionObj List::value_at_index(size_t i) { - ExpressionObj obj = this->at(i); - if (is_arglist_) { - if (Argument* arg = Cast(obj)) { - return arg->value(); - } else { - return obj; - } - } else { - return obj; - } + void CustomWarning::accept(ValueVisitor* visitor) { + throw std::runtime_error("CustomWarning::accept not implemented"); + } + Value* CustomWarning::accept(ValueVisitor* visitor) { + throw std::runtime_error("CustomWarning::accept not implemented"); } ///////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////// - Map::Map(SourceSpan pstate, size_t size) - : Value(pstate), - Hashed(size) - { concrete_type(MAP); } - - 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; + Calculation::Calculation(const SourceSpan& pstate, const sass::string& name, + const sass::vector arguments) : + Value(pstate), + name_(name), + arguments_(arguments) + {} + + Calculation::Calculation(const Calculation* ptr) + : Value(ptr) + {} + /// Returns whether [character] intrinsically needs parentheses if it appears + /// in the unquoted string argument of a `calc()` being embedded in another + /// calculation. + static bool _charNeedsParentheses(uint8_t character) { + return Character::isWhitespace(character) + || character == Character::$asterisk + || character == Character::$slash; + } + + + /// Returns whether [text] needs parentheses if it's the contents of a + /// `calc()` being embedded in another calculation. + static bool _needsParentheses(const sass::string& text) { + auto first = text[0]; // .codeUnitAt(0); + if (_charNeedsParentheses(first)) return true; + auto couldBeVar = text.size() >= 4 && + Character::characterEqualsIgnoreCase(first, Character::$v); + + if (text.size() < 2) return false; + auto second = text[1]; // .codeUnitAt(1); + if (_charNeedsParentheses(second)) return true; + couldBeVar = couldBeVar && Character::characterEqualsIgnoreCase(second, Character::$a); + + if (text.size() < 3) return false; + auto third = text[2]; // .codeUnitAt(2); + if (_charNeedsParentheses(third)) return true; + couldBeVar = couldBeVar && Character::characterEqualsIgnoreCase(third, Character::$r); + + if (text.size() < 4) return false; + auto fourth = text[3]; // .codeUnitAt(3); + if (couldBeVar && fourth == Character::$lparen) return true; + if (_charNeedsParentheses(fourth)) return true; + + for (size_t i = 4; i < text.size(); i++) { + if (_charNeedsParentheses(text[i]/*.codeUnitAt(i)*/)) return true; } - // compare/sort by type - return type() < rhs.type(); + return false; } - bool Map::operator== (const Expression& rhs) const + + AstNode* Calculation::simplify(Logger& logger) { - 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; + if (name_ == str_calc) { + if (arguments_.empty()) std::cerr << "arguments empty!!!\n"; + else if (arguments_.size() > 1) std::cerr << "too many args!!!\n"; + else { + const AstNode* arg = arguments_[0]; + const String* str = dynamic_cast(arg); + if (str != nullptr && str->hasQuotes() == false) { + if (_needsParentheses(str->value())) { + sass::string quoted("(" + str->value() + ")"); + return new String(pstate_, std::move(quoted), false); + } + } + return arguments_[0]; } - return true; } - return false; + // Or return ourself again + return this; + } + + bool Calculation::operator== (const Value& rhs) const + { + throw "not implemented Calc=="; + return false; // rhs.isNull(); } - List_Obj Map::to_list(SourceSpan& pstate) { - List_Obj ret = SASS_MEMORY_NEW(List, pstate, length(), SASS_COMMA); + Value* Calculation::plus(Value* other, Logger& logger, const SourceSpan& pstate) const + { + if (auto str = other->isaString()) return Value::plus(other, logger, pstate); + callStackFrame csf(logger, pstate); + throw Exception::SassScriptException(logger, pstate, + "Undefined operation \"" + toCss() + " + " + other->toCss() + "\"."); + } - for (auto key : keys()) { - List_Obj l = SASS_MEMORY_NEW(List, pstate, 2); - l->append(key); - l->append(at(key)); - ret->append(l); - } + Value* Calculation::minus(Value* other, Logger& logger, const SourceSpan& pstate) const + { + if (auto str = other->isaString()) return Value::minus(other, logger, pstate); + callStackFrame csf(logger, pstate); + throw Exception::SassScriptException(logger, pstate, + "Undefined operation \"" + toCss() + " - " + other->toCss() + "\"."); + } - return ret; + // The SassScript unary `+` operation. + Value* Calculation::unaryPlus(Logger& logger, const SourceSpan& pstate) const + { + callStackFrame csf(logger, pstate); + throw Exception::SassScriptException(logger, pstate, + "Undefined operation \"+" + toCss() + "\"."); } - size_t Map::hash() const + // The SassScript unary `-` operation. + Value* Calculation::unaryMinus(Logger& logger, const SourceSpan& pstate) const { - if (hash_ == 0) { - for (auto key : keys()) { - hash_combine(hash_, key->hash()); - hash_combine(hash_, at(key)->hash()); - } - } + callStackFrame csf(logger, pstate); + throw Exception::SassScriptException(logger, pstate, + "Undefined operation \"-" + toCss() + "\"."); + } - return hash_; + size_t Calculation::hash() const + { + return typeid(Calculation).hash_code(); } ///////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////// - Binary_Expression::Binary_Expression(SourceSpan pstate, - Operand op, ExpressionObj lhs, ExpressionObj rhs) - : PreValue(pstate), op_(op), left_(lhs), right_(rhs), hash_(0) - { } + Null::Null(const SourceSpan& pstate) + : Value(pstate) + {} - Binary_Expression::Binary_Expression(const Binary_Expression* ptr) - : PreValue(ptr), - op_(ptr->op_), - left_(ptr->left_), - right_(ptr->right_), - hash_(ptr->hash_) - { } + Null::Null(const Null* ptr) + : Value(ptr) + {} - bool Binary_Expression::is_left_interpolant(void) const + bool Null::operator== (const Value& rhs) const { - return is_interpolant() || (left() && left()->is_left_interpolant()); + return rhs.isNull(); } - bool Binary_Expression::is_right_interpolant(void) const + + size_t Null::hash() const { - return is_interpolant() || (right() && right()->is_right_interpolant()); + return typeid(Null).hash_code(); } - const sass::string Binary_Expression::type_name() + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + Color::Color( + const SourceSpan& pstate, + double alpha, + const sass::string& disp, + bool parsed) : + Value(pstate), + disp_(disp), + a_(alpha), + parsed_(parsed) + {} + + Color::Color(const Color* ptr) + : Value(ptr), + // Reset on copy + // disp_(ptr->disp_), + a_(ptr->a_), + parsed_(false) // safe to assume? + // ptr->parsed_ + {} + + ///////////////////////////////////////////////////////////////////////// + // Implement value operators for color + ///////////////////////////////////////////////////////////////////////// + + Value* Color::plus(Value* other, Logger& logger, const SourceSpan& pstate) const { - return sass_op_to_name(optype()); + if (other->isaNumber() || other->isaColor()) { + callStackFrame csf(logger, pstate); + throw Exception::SassScriptException( + "Undefined operation \"" + inspect() + + " + " + other->inspect() + "\".", + logger, pstate); + } + return Value::plus(other, logger, pstate); } - const sass::string Binary_Expression::separator() + Value* Color::minus(Value* other, Logger& logger, const SourceSpan& pstate) const { - return sass_op_separator(optype()); + if (other->isaNumber() || other->isaColor()) { + callStackFrame csf(logger, pstate); + throw Exception::SassScriptException( + "Undefined operation \"" + inspect() + + " - " + other->inspect() + "\".", + logger, pstate); + } + return Value::minus(other, logger, pstate); } - bool Binary_Expression::has_interpolant() const + Value* Color::dividedBy(Value* other, Logger& logger, const SourceSpan& pstate) const { - return is_left_interpolant() || - is_right_interpolant(); + if (other->isaNumber() || other->isaColor()) { + callStackFrame csf(logger, pstate); + throw Exception::SassScriptException( + "Undefined operation \"" + inspect() + + " / " + other->inspect() + "\".", + logger, pstate); + } + return Value::dividedBy(other, logger, pstate); } - void Binary_Expression::set_delayed(bool delayed) + Value* Color::modulo(Value* other, Logger& logger, const SourceSpan& pstate) const { - right()->set_delayed(delayed); - left()->set_delayed(delayed); - is_delayed(delayed); + callStackFrame csf(logger, pstate); + throw Exception::SassScriptException( + "Undefined operation \"" + inspect() + + " % " + other->inspect() + "\".", + logger, pstate); } - bool Binary_Expression::operator<(const Expression& rhs) const + Value* Color::remainder(Value* other, Logger& logger, const SourceSpan& pstate) const { - if (auto m = Cast(&rhs)) { - return type() < m->type() || - *left() < *m->left() || - *right() < *m->right(); - } - // compare/sort by type - return type() < rhs.type(); + callStackFrame csf(logger, pstate); + throw Exception::SassScriptException( + "Undefined operation \"" + inspect() + + " % " + other->inspect() + "\".", + logger, pstate); } - bool Binary_Expression::operator==(const Expression& rhs) const + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + ColorRgba::ColorRgba( + const SourceSpan& pstate, + double red, + double green, + double blue, + double alpha, + const sass::string& disp, + bool parsed) : + Color(pstate, alpha, disp, parsed), + r_(red), + g_(green), + b_(blue) + {} + + ColorRgba::ColorRgba(const ColorRgba* ptr) + : Color(ptr), + r_(ptr->r_), + g_(ptr->g_), + b_(ptr->b_) + {} + + ///////////////////////////////////////////////////////////////////////// + + bool ColorRgba::operator== (const Value& rhs) const { - if (auto m = Cast(&rhs)) { - return type() == m->type() && - *left() == *m->left() && - *right() == *m->right(); + if (const Color* color = rhs.isaColor()) { + ColorRgba* rgba = color->toRGBA(); + return *this == *rgba; } return false; } - size_t Binary_Expression::hash() const + bool ColorRgba::operator== (const ColorRgba& rhs) const + { + return r_ == rhs.r() && + g_ == rhs.g() && + b_ == rhs.b() && + a_ == rhs.a(); + } + + size_t ColorRgba::hash() const { if (hash_ == 0) { - hash_ = std::hash()(optype()); - hash_combine(hash_, left()->hash()); - hash_combine(hash_, right()->hash()); + hash_start(hash_, typeid(ColorRgba).hash_code()); + hash_combine(hash_, doubleHasher(a_)); + hash_combine(hash_, doubleHasher(r_)); + hash_combine(hash_, doubleHasher(g_)); + hash_combine(hash_, doubleHasher(b_)); } return hash_; } ///////////////////////////////////////////////////////////////////////// - ///////////////////////////////////////////////////////////////////////// - Function::Function(SourceSpan pstate, Definition_Obj def, bool css) - : Value(pstate), definition_(def), is_css_(css) - { concrete_type(FUNCTION_VAL); } + ColorHsla* ColorRgba::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; + } - Function::Function(const Function* ptr) - : Value(ptr), definition_(ptr->definition_), is_css_(ptr->is_css_) - { concrete_type(FUNCTION_VAL); } + // HSL hsl_struct; + h = h * 60; + s = s * 100; + l = l * 100; + + return SASS_MEMORY_NEW(ColorHsla, + pstate(), h, s, l, a(), "" + ); + } - bool Function::operator< (const Expression& rhs) const + ColorHwba* ColorRgba::copyAsHWBA() 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(); + + // Algorithm from http://en.wikipedia.org/wiki/wHSL_and_HSV#Conversion_from_RGB_to_HSL_or_HSV + double r = r_ / 255.0; + double g = g_ / 255.0; + double b = b_ / 255.0; + + double max = std::max(r, std::max(g, b)); + double min = std::min(r, std::min(g, b)); + double delta = max - min; + + double h = 0; + + if (NEAR_EQUAL(max, min)) { + h = 0; // achromatic + } + else { + if (r == max) h = (g - b) / delta + (g < b ? 6 : 0); + else if (g == max) h = (b - r) / delta + 2; + else if (b == max) h = (r - g) / delta + 4; } - // compare/sort by type - return type() < rhs.type(); + + double _w = std::min(r, std::min(g, b)); + double _b = 1.0 - std::max(r, std::max(g, b)); + + // HSL hsl_struct; + h = h * 60; + _w *= 100; + _b *= 100; + + return SASS_MEMORY_NEW(ColorHwba, pstate_, h, _w, _b, a_); + + } + + ColorHsla* ColorRgba::toHSLA() const + { + return copyAsHSLA(); + } + + ColorHwba* ColorRgba::toHWBA() const + { + return copyAsHWBA(); + } + + ColorRgba* ColorRgba::copyAsRGBA() const + { + return SASS_MEMORY_COPY(this); } - bool Function::operator== (const Expression& rhs) const + ColorRgba* ColorRgba::toRGBA() const + { + // This is safe, I know what I do! + return const_cast(this); + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + ColorHsla::ColorHsla( + const SourceSpan& pstate, + double hue, + double saturation, + double lightness, + double alpha, + const sass::string& disp, + bool parsed) : + Color(pstate, alpha, disp, parsed), + h_(absmod(hue, 360.0)), + s_(clamp(saturation, 0.0, 100.0)), + l_(clamp(lightness, 0.0, 100.0)) + {} + + ColorHsla::ColorHsla(const ColorHsla* ptr) + : Color(ptr), + h_(ptr->h_), + s_(ptr->s_), + l_(ptr->l_) + {} + + ///////////////////////////////////////////////////////////////////////// + + bool ColorHsla::operator== (const Value& 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(); + if (const Color* color = rhs.isaColor()) { + ColorHsla* hsla = color->toHSLA(); + return *this == *hsla; } return false; } - sass::string Function::name() { - if (definition_) { - return definition_->name(); + bool ColorHsla::operator== (const ColorHsla& rhs) const + { + return h_ == rhs.h() && + s_ == rhs.s() && + l_ == rhs.l() && + a_ == rhs.a(); + } + + size_t ColorHsla::hash() const + { + if (hash_ == 0) { + hash_start(hash_, typeid(ColorHsla).hash_code()); + hash_combine(hash_, doubleHasher(a_)); + hash_combine(hash_, doubleHasher(h_)); + hash_combine(hash_, doubleHasher(s_)); + hash_combine(hash_, doubleHasher(l_)); } - return ""; + return hash_; } ///////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////// - Function_Call::Function_Call(SourceSpan pstate, String_Obj n, Arguments_Obj args, void* cookie) - : PreValue(pstate), sname_(n), arguments_(args), func_(), via_call_(false), cookie_(cookie), hash_(0) - { concrete_type(FUNCTION); } - Function_Call::Function_Call(SourceSpan pstate, String_Obj n, Arguments_Obj args, Function_Obj func) - : PreValue(pstate), sname_(n), arguments_(args), func_(func), via_call_(false), cookie_(0), hash_(0) - { concrete_type(FUNCTION); } - Function_Call::Function_Call(SourceSpan pstate, String_Obj n, Arguments_Obj args) - : PreValue(pstate), sname_(n), arguments_(args), via_call_(false), cookie_(0), hash_(0) - { concrete_type(FUNCTION); } - - Function_Call::Function_Call(SourceSpan pstate, sass::string n, Arguments_Obj args, void* cookie) - : PreValue(pstate), sname_(SASS_MEMORY_NEW(String_Constant, pstate, n)), arguments_(args), func_(), via_call_(false), cookie_(cookie), hash_(0) - { concrete_type(FUNCTION); } - Function_Call::Function_Call(SourceSpan pstate, sass::string n, Arguments_Obj args, Function_Obj func) - : PreValue(pstate), sname_(SASS_MEMORY_NEW(String_Constant, pstate, n)), arguments_(args), func_(func), via_call_(false), cookie_(0), hash_(0) - { concrete_type(FUNCTION); } - Function_Call::Function_Call(SourceSpan pstate, sass::string n, Arguments_Obj args) - : PreValue(pstate), sname_(SASS_MEMORY_NEW(String_Constant, pstate, n)), arguments_(args), via_call_(false), cookie_(0), hash_(0) - { concrete_type(FUNCTION); } + ColorHwba::ColorHwba( + const SourceSpan& pstate, + double hue, + double whiteness, + double blackness, + double alpha, + const sass::string& disp, + bool parsed) : + Color(pstate, alpha, disp, parsed), + h_(absmod(hue, 360.0)), + w_(clamp(whiteness, 0.0, 100.0)), + b_(clamp(blackness, 0.0, 100.0)) + {} + + ColorHwba::ColorHwba(const ColorHwba* ptr) + : Color(ptr), + h_(ptr->h_), + w_(ptr->w_), + b_(ptr->b_) + {} - Function_Call::Function_Call(const Function_Call* ptr) - : PreValue(ptr), - sname_(ptr->sname_), - arguments_(ptr->arguments_), - func_(ptr->func_), - via_call_(ptr->via_call_), - cookie_(ptr->cookie_), - hash_(ptr->hash_) - { concrete_type(FUNCTION); } + ///////////////////////////////////////////////////////////////////////// - bool Function_Call::operator==(const Expression& rhs) const + bool ColorHwba::operator== (const Value& rhs) const { - if (auto m = Cast(&rhs)) { - if (*sname() != *m->sname()) return false; - if (arguments()->length() != m->arguments()->length()) return false; - for (size_t i = 0, L = arguments()->length(); i < L; ++i) - if (*arguments()->get(i) != *m->arguments()->get(i)) return false; - return true; + if (const Color* color = rhs.isaColor()) { + ColorHwba* hwba = color->toHWBA(); + return *this == *hwba; } return false; } - size_t Function_Call::hash() const + bool ColorHwba::operator== (const ColorHwba& rhs) const + { + return h_ == rhs.h() && + w_ == rhs.w() && + b_ == rhs.b() && + a_ == rhs.a(); + } + + size_t ColorHwba::hash() const { if (hash_ == 0) { - hash_ = std::hash()(name()); - for (auto argument : arguments()->elements()) - hash_combine(hash_, argument->hash()); + hash_start(hash_, typeid(ColorHsla).hash_code()); + hash_combine(hash_, doubleHasher(a_)); + hash_combine(hash_, doubleHasher(h_)); + hash_combine(hash_, doubleHasher(w_)); + hash_combine(hash_, doubleHasher(b_)); } return hash_; } - sass::string Function_Call::name() const + + ColorHwba* ColorHwba::copyAsHWBA() const { - return sname(); + return SASS_MEMORY_COPY(this); } - bool Function_Call::is_css() { - if (func_) return func_->is_css(); - return false; + ColorRgba* ColorHwba::copyAsRGBA() const + { + double h = h_ / 360.0; + double wh = w_ / 100.0; + double bl = b_ / 100.0; + double ratio = wh + bl; + double v, f, n; + if (ratio > 1) { + wh /= ratio; + bl /= ratio; + } + int i = (int)floor(6.0 * h); + v = 1.0 - bl; + f = 6.0 * h - i; + if ((i & 1) != 0) { + f = 1 - f; + } + n = wh + f * (v - wh); + double r, g, b; + switch (i) { + default: + case 6: + case 0: r = v; g = n; b = wh; break; + case 1: r = n; g = v; b = wh; break; + case 2: r = wh; g = v; b = n; break; + case 3: r = wh; g = n; b = v; break; + case 4: r = n; g = wh; b = v; break; + case 5: r = v; g = wh; b = n; break; + } + return SASS_MEMORY_NEW(ColorRgba, + pstate_, r * 255.0, g * 255.0, b * 255.0, a_); } - ///////////////////////////////////////////////////////////////////////// - ///////////////////////////////////////////////////////////////////////// - - Variable::Variable(SourceSpan pstate, sass::string n) - : PreValue(pstate), name_(n) - { concrete_type(VARIABLE); } + ColorHsla* ColorHwba::copyAsHSLA() const + { + ColorRgbaObj rgba(copyAsRGBA()); + return rgba->copyAsHSLA(); + } - Variable::Variable(const Variable* ptr) - : PreValue(ptr), name_(ptr->name_) - { concrete_type(VARIABLE); } + ColorHsla* ColorHwba::toHSLA() const + { + return copyAsHSLA(); + } - bool Variable::operator==(const Expression& rhs) const + ColorHwba* ColorHwba::toHWBA() const { - if (auto e = Cast(&rhs)) { - return name() == e->name(); - } - return false; + return const_cast(this);; } - size_t Variable::hash() const + ColorRgba* ColorHwba::toRGBA() const { - return std::hash()(name()); + return copyAsRGBA(); } - ///////////////////////////////////////////////////////////////////////// + + ///////////////////////////////////////////////////////////////////////// - Number::Number(SourceSpan pstate, double val, sass::string u, bool zero) - : Value(pstate), - Units(), - value_(val), - zero_(zero), - hash_(0) + // hue to RGB helper function + double h_to_rgb(double m1, double m2, double h) { - size_t l = 0; - size_t r; - if (!u.empty()) { - bool nominator = true; - while (true) { - r = u.find_first_of("*/", l); - sass::string unit(u.substr(l, r == sass::string::npos ? r : r - l)); - if (!unit.empty()) { - if (nominator) numerators.push_back(unit); - else denominators.push_back(unit); - } - if (r == sass::string::npos) break; - // ToDo: should error for multiple slashes - // if (!nominator && u[r] == '/') error(...) - if (u[r] == '/') - nominator = false; - // strange math parsing? - // else if (u[r] == '*') - // nominator = true; - l = r + 1; - } - } - concrete_type(NUMBER); + h = absmod(h, 1.0); + if (h * 6.0 < 1) return m1 + (m2 - m1) * h * 6; + if (h * 2.0 < 1) return m2; + if (h * 3.0 < 2) return m1 + (m2 - m1) * (2.0 / 3.0 - h) * 6; + return m1; } - Number::Number(const Number* ptr) - : Value(ptr), - Units(ptr), - value_(ptr->value_), zero_(ptr->zero_), - hash_(ptr->hash_) - { concrete_type(NUMBER); } + ColorRgba* ColorHsla::copyAsRGBA() const + { + double h = absmod(h_ / 360.0, 1.0); + double s = clamp(s_ / 100.0, 0.0, 1.0); + double l = clamp(l_ / 100.0, 0.0, 1.0); + + // Algorithm from the CSS3 spec: http://www.w3.org/TR/css3-color/#hsl-color. + double m2; + if (l <= 0.5) m2 = l * (s + 1.0); + else m2 = (l + s) - (l * s); + double m1 = (l * 2.0) - m2; + // round the results -- consider moving this into the Color constructor + double r = (h_to_rgb(m1, m2, h + 1.0 / 3.0) * 255.0); + double g = (h_to_rgb(m1, m2, h) * 255.0); + double b = (h_to_rgb(m1, m2, h - 1.0 / 3.0) * 255.0); + + return SASS_MEMORY_NEW(ColorRgba, + pstate(), r, g, b, a(), "" + ); + } - // cancel out unnecessary units - void Number::reduce() + ColorHwba* ColorHsla::copyAsHWBA() const { - // apply conversion factor - value_ *= this->Units::reduce(); + ColorRgbaObj rgba(copyAsRGBA()); + return rgba->copyAsHWBA(); + + throw std::runtime_error("invalid"); + return nullptr; } - void Number::normalize() + ColorHsla* ColorHsla::copyAsHSLA() const { - // apply conversion factor - value_ *= this->Units::normalize(); + auto col = SASS_MEMORY_COPY(this); + col->parsed(false); // Do better + return col; } - size_t Number::hash() const + ColorRgba* ColorHsla::toRGBA() const { - if (hash_ == 0) { - hash_ = std::hash()(value_); - for (const auto& numerator : numerators) - hash_combine(hash_, std::hash()(numerator)); - for (const auto& denominator : denominators) - hash_combine(hash_, std::hash()(denominator)); - } - return hash_; + return copyAsRGBA(); } - bool Number::operator< (const Expression& rhs) const + ColorHwba* ColorHsla::toHWBA() const { - if (auto n = Cast(&rhs)) { - return *this < *n; + return copyAsHWBA(); + } + + ColorHsla* ColorHsla::toHSLA() const + { + // This is safe, I know what I do! + return const_cast(this); + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // Value constructor + Number::Number( + const SourceSpan& pstate, + double value, + const sass::string& units) : + Value(pstate), + Units(units), + value_(value), + lhsAsSlash_(), + rhsAsSlash_() + {} + + // Value constructor + Number::Number( + const SourceSpan& pstate, + double value, + Units units) : + Value(pstate), + Units(units), + value_(value), + lhsAsSlash_(), + rhsAsSlash_() + {} + + + // Copy constructor + Number::Number(const Number* ptr) : + Value(ptr), + Units(ptr), + value_(ptr->value_), + lhsAsSlash_(ptr->lhsAsSlash_), + rhsAsSlash_(ptr->rhsAsSlash_) + {} + + ///////////////////////////////////////////////////////////////////////// + // Implement base value equality comparator + ///////////////////////////////////////////////////////////////////////// + + // Helper to determine if we can work with both numbers directly + bool isSimpleNumberComparison(const Number& lhs, const Number& rhs) + { + // Gather statistics from the units + size_t l_n_units = lhs.numerators.size(); + size_t r_n_units = rhs.numerators.size(); + size_t l_d_units = lhs.denominators.size(); + size_t r_d_units = rhs.denominators.size(); + size_t l_units = l_n_units + l_d_units; + size_t r_units = r_n_units + r_d_units; + + // Old ruby sass behavior (deprecated) + if (l_units == 0) return true; + if (r_units == 0) return true; + + // check if both sides have exactly the same units + if (l_n_units == r_n_units && l_d_units == r_d_units) { + return (lhs.numerators == rhs.numerators) + && (lhs.denominators == rhs.denominators); } + return false; } + // EO isSimpleNumberComparison - bool Number::operator== (const Expression& rhs) const + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + bool Number::operator== (const Value& rhs) const { - if (auto n = Cast(&rhs)) { - return *this == *n; + if (const Number* number = rhs.isaNumber()) { + return *this == *number; } return false; } bool Number::operator== (const Number& rhs) const { - // unitless or only having one unit are equivalent (3.4) - // therefore we need to reduce the units beforehand - Number l(*this), r(rhs); l.reduce(); r.reduce(); - size_t lhs_units = l.numerators.size() + l.denominators.size(); - size_t rhs_units = r.numerators.size() + r.denominators.size(); - if (!lhs_units || !rhs_units) { - return NEAR_EQUAL(l.value(), r.value()); + if (isUnitless() && rhs.isUnitless()) { + return NEAR_EQUAL_INF(value(), rhs.value()); } - // ensure both have same units + // Ignore units in certain cases + // if (isSimpleNumberComparison(*this, rhs)) { + // return NEAR_EQUAL(value(), rhs.value()); + // } + // Otherwise we need copies + Number l(*this), r(rhs); + // Reduce and normalize + l.reduce(); r.reduce(); l.normalize(); r.normalize(); - Units &lhs_unit = l, &rhs_unit = r; - return lhs_unit == rhs_unit && - NEAR_EQUAL(l.value(), r.value()); + // Ensure both have same units + return l.Units::operator==(r) && + NEAR_EQUAL_INF(l.value(), r.value()); } - bool Number::operator< (const Number& rhs) const + size_t Number::hash() const { - // unitless or only having one unit are equivalent (3.4) - // therefore we need to reduce the units beforehand - Number l(*this), r(rhs); l.reduce(); r.reduce(); - size_t lhs_units = l.numerators.size() + l.denominators.size(); - size_t rhs_units = r.numerators.size() + r.denominators.size(); - if (!lhs_units || !rhs_units) { - return l.value() < r.value(); - } - // ensure both have same units - l.normalize(); r.normalize(); - Units &lhs_unit = l, &rhs_unit = r; - if (!(lhs_unit == rhs_unit)) { - /* ToDo: do we always get useful backtraces? */ - throw Exception::IncompatibleUnits(rhs, *this); - } - if (lhs_unit == rhs_unit) { - return l.value() < r.value(); - } else { - return lhs_unit < rhs_unit; + if (hash_ == 0) { + hash_start(hash_, doubleHasher(value_)); + for (const auto& numerator : numerators) + hash_combine(hash_, stringHasher(numerator)); + for (const auto& denominator : denominators) + hash_combine(hash_, stringHasher(denominator)); } + return hash_; } ///////////////////////////////////////////////////////////////////////// + // Implement value comparators for number ///////////////////////////////////////////////////////////////////////// - Color::Color(SourceSpan pstate, double a, const sass::string disp) - : Value(pstate), - disp_(disp), a_(a), - hash_(0) - { concrete_type(COLOR); } - - Color::Color(const Color* ptr) - : Value(ptr->pstate()), - // reset on copy - disp_(""), - a_(ptr->a_), - hash_(ptr->hash_) - { concrete_type(COLOR); } - - bool Color::operator< (const Expression& rhs) const + bool Number::greaterThan(Value* other, Logger& logger, const SourceSpan& pstate) const { - if (auto r = Cast(&rhs)) { - return *this < *r; - } - else if (auto r = Cast(&rhs)) { - return *this < *r; - } - else if (auto r = Cast(&rhs)) { - return a_ < r->a(); + if (Number* rhs = other->isaNumber()) { + // Ignore units in certain cases + if (isSimpleNumberComparison(*this, *rhs)) { + return value() > rhs->value(); + } + // Otherwise we need copies + Number l(*this), r(*rhs); + // Reduce and normalize + l.reduce(); r.reduce(); + l.normalize(); r.normalize(); + // Ensure both have same units + if (l.Units::operator==(r)) { + return l.value() > r.value(); + } + // Throw error, unit are incompatible + callStackFrame csf(logger, pstate); + throw Exception::UnitMismatch( + logger, this, rhs); } - // compare/sort by type - return type() < rhs.type(); + callStackFrame csf(logger, pstate); + throw Exception::SassScriptException( + "Undefined operation \"" + inspect() + + " > " + other->inspect() + "\".", + logger, pstate); } + // EO greaterThan - bool Color::operator== (const Expression& rhs) const + bool Number::greaterThanOrEquals(Value* other, Logger& logger, const SourceSpan& pstate) const { - if (auto r = Cast(&rhs)) { - return *this == *r; + if (Number* rhs = other->isaNumber()) { + // Ignore units in certain cases + if (isSimpleNumberComparison(*this, *rhs)) { + return value() >= rhs->value(); + } + // Otherwise we need copies + Number l(*this), r(*rhs); + // Reduce and normalize + l.reduce(); r.reduce(); + l.normalize(); r.normalize(); + // Ensure both have same units + if (l.Units::operator==(r)) { + return l.value() >= r.value(); + } + // Throw error, unit are incompatible + callStackFrame csf(logger, pstate); + throw Exception::UnitMismatch( + logger, this, rhs); } - else if (auto r = Cast(&rhs)) { - return *this == *r; + callStackFrame csf(logger, pstate); + throw Exception::SassScriptException( + "Undefined operation \"" + inspect() + + " >= " + other->inspect() + "\".", + logger, pstate); + } + // EO greaterThanOrEquals + + bool Number::lessThan(Value* other, Logger& logger, const SourceSpan& pstate) const + { + if (Number* rhs = other->isaNumber()) { + // Ignore units in certain cases + if (isSimpleNumberComparison(*this, *rhs)) { + return value() < rhs->value(); + } + // Otherwise we need copies + Number l(*this), r(*rhs); + // Reduce and normalize + l.reduce(); r.reduce(); + l.normalize(); r.normalize(); + // Ensure both have same units + if (l.Units::operator==(r)) { + return l.value() < r.value(); + } + // Throw error, unit are incompatible + callStackFrame csf(logger, pstate); + throw Exception::UnitMismatch( + logger, this, rhs); } - else if (auto r = Cast(&rhs)) { - return a_ == r->a(); + callStackFrame csf(logger, pstate); + throw Exception::SassScriptException( + "Undefined operation \"" + inspect() + + " < " + other->inspect() + "\".", + logger, pstate); + } + // EO lessThan + + bool Number::lessThanOrEquals(Value* other, Logger& logger, const SourceSpan& pstate) const + { + if (Number* rhs = other->isaNumber()) { + // Ignore units in certain cases + if (isSimpleNumberComparison(*this, *rhs)) { + return value() <= rhs->value(); + } + // Otherwise we need copies + Number l(*this), r(*rhs); + // Reduce and normalize + l.reduce(); r.reduce(); + l.normalize(); r.normalize(); + // Ensure both have same units + if (l.Units::operator==(r)) { + return l.value() <= r.value(); + } + // Throw error, unit are incompatible + callStackFrame csf(logger, pstate); + throw Exception::UnitMismatch( + logger, this, rhs); } - return false; + callStackFrame csf(logger, pstate); + throw Exception::SassScriptException( + "Undefined operation \"" + inspect() + + " <= " + other->inspect() + "\".", + logger, pstate); } + // EO lessThanOrEquals ///////////////////////////////////////////////////////////////////////// + // Helper functions to do the raw value operations ///////////////////////////////////////////////////////////////////////// - Color_RGBA::Color_RGBA(SourceSpan pstate, double r, double g, double b, double a, const sass::string disp) - : Color(pstate, a, disp), - r_(r), g_(g), b_(b) - { concrete_type(COLOR); } - - Color_RGBA::Color_RGBA(const Color_RGBA* ptr) - : Color(ptr), - r_(ptr->r_), - g_(ptr->g_), - b_(ptr->b_) - { concrete_type(COLOR); } - bool Color_RGBA::operator< (const Expression& rhs) const + // Local functions that implement the value operation + inline double add(double x, double y) { return x + y; } + inline double sub(double x, double y) { return x - y; } + inline double mul(double x, double y) { return x * y; } + inline double div(double x, double y) { return x / y; } + inline double mod(double x, double y) { - if (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(); + // ToDo: move this special case to mod operator +// else if (op == mod && std::isinf(rval) && std::isinf(copy->value())) { +// copy->value(std::numeric_limits::quiet_NaN()); +// } + + //if (std::isinf(x) && std::isinf(y)) { + // return NaN; + //} + + // Always the case in dart sass + if (std::isinf(x)) return NaN; + // Next case is a bit complicated and not super well defined in Math + if (std::isinf(y) && std::signbit(y) != std::signbit(x)) return NaN; + + if ((x > 0 && y < 0) || (x < 0 && y > 0)) { + double ret = std::fmod(x, y); + return ret ? ret + y : ret; + } + else { + double ret = std::fmod(x, y); + return ret; } - return false; } - - size_t Color_RGBA::hash() const + inline double rem(double x, double y) { - if (hash_ == 0) { - hash_ = std::hash()("RGBA"); - hash_combine(hash_, std::hash()(a_)); - hash_combine(hash_, std::hash()(r_)); - hash_combine(hash_, std::hash()(g_)); - hash_combine(hash_, std::hash()(b_)); + if ((x > 0 && y < 0) || (x < 0 && y > 0)) { + double ret = std::remainder(x, y); + return ret ? ret + y : ret; + } + else { + return std::remainder(x, y); } - return hash_; } - Color_HSLA* Color_RGBA::copyAsHSLA() const - { + ///////////////////////////////////////////////////////////////////////// + // Implement value operators for number + ///////////////////////////////////////////////////////////////////////// - // 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; + Value* Number::operate(double (*op)(double, double), const Number& rhs, Logger& logger, const SourceSpan& pstate) const + { - double max = std::max(r, std::max(g, b)); - double min = std::min(r, std::min(g, b)); - double delta = max - min; + size_t l_n_units = numerators.size(); + size_t l_d_units = denominators.size(); + size_t r_n_units = rhs.numerators.size(); + size_t r_d_units = rhs.denominators.size(); + size_t l_units = l_n_units + l_d_units; + size_t r_units = r_n_units + r_d_units; - double h = 0; - double s; - double l = (max + min) / 2.0; + double lval = value(); + double rval = rhs.value(); - if (NEAR_EQUAL(max, min)) { - h = s = 0; // achromatic + // Catch modulo by zero + /*if (op == mod && rval == 0) { + return SASS_MEMORY_NEW(Number, pstate, + std::numeric_limits::quiet_NaN()); } - 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; + // Catch division by zero + else */ if (op == div && rval == 0) { + Units units(this); // Copy left units + units.numerators.insert(units.numerators.end(), + rhs.denominators.begin(), rhs.denominators.end()); + units.denominators.insert(units.denominators.end(), + rhs.numerators.begin(), rhs.numerators.end()); + // Do a logical XOR to have one or the other side negative + if (std::signbit(lval) != std::signbit(rval)) return SASS_MEMORY_NEW( + Number, pstate, - std::numeric_limits::infinity(), units); + else if (lval != 0) return SASS_MEMORY_NEW(Number, pstate, + std::numeric_limits::infinity(), units); + else return SASS_MEMORY_NEW(Number, pstate, + std::numeric_limits::quiet_NaN(), units); } - // HSL hsl_struct; - h = h * 60; - s = s * 100; - l = l * 100; + // Simplest case with no units + // Just operate on the values + if (r_units == 0 && l_units <= 1) { + Number* copy = SASS_MEMORY_COPY(this); + copy->value(op(lval, rval)); + copy->pstate(pstate); + return copy; + } + // Left hand has no unit, so we can just copy + // the units from the right hand side. If units + // are not compatible, op function will throw! + if (l_units == 0 && r_units == 1) { + Number* copy = SASS_MEMORY_COPY(this); + copy->value(op(lval, rval)); + // Switch units for division + if (op == div) { + copy->numerators = rhs.denominators; + copy->denominators = rhs.numerators; + } + else { + copy->numerators = rhs.numerators; + copy->denominators = rhs.denominators; + } + copy->pstate(pstate); + return copy; + } + // Both sides have exactly one unit + // Most used case, so optimize it too! + if (l_units == 1 && r_units == 1) { + if (numerators == rhs.numerators) { + if (denominators == rhs.denominators) { + Number* copy = SASS_MEMORY_COPY(this); + copy->value(op(lval, rval)); + + if (op == div) { + copy->numerators.clear(); + copy->denominators.clear(); + } + else if (op == mul) { + copy->numerators.insert(copy->numerators.end(), + rhs.numerators.begin(), rhs.numerators.end()); + copy->denominators.insert(copy->denominators.end(), + rhs.denominators.begin(), rhs.denominators.end()); + } + copy->pstate(pstate); + return copy; + } + } + } - return SASS_MEMORY_NEW(Color_HSLA, - pstate(), h, s, l, a(), "" - ); - } + // Otherwise we go into the generic operation + NumberObj copy = SASS_MEMORY_COPY(this); - Color_RGBA* Color_RGBA::copyAsRGBA() const - { - return SASS_MEMORY_COPY(this); - } + // std::cerr << "OPERATE " << inspect() << " " << rhs.inspect() << "\n"; - ///////////////////////////////////////////////////////////////////////// - ///////////////////////////////////////////////////////////////////////// + // Move right units for some operations if left has none yet + if (isUnitless() && (op == add || op == sub || op == mod)) { + copy->numerators = rhs.numerators; + copy->denominators = rhs.denominators; + } - Color_HSLA::Color_HSLA(SourceSpan pstate, double h, double s, double l, double a, const sass::string disp) - : Color(pstate, a, disp), - h_(absmod(h, 360.0)), - s_(clip(s, 0.0, 100.0)), - l_(clip(l, 0.0, 100.0)) - // hash_(0) - { concrete_type(COLOR); } + if (op == mul) { + // Multiply the values + copy->value(op(lval, rval)); + // Add all units for multiplications + copy->numerators.insert(copy->numerators.end(), + rhs.numerators.begin(), rhs.numerators.end()); + copy->denominators.insert(copy->denominators.end(), + rhs.denominators.begin(), rhs.denominators.end()); + // Do logical unit cleanup + copy->reduce(); + } + else if (op == div) { + // Divide the values + copy->value(op(lval, rval)); + // Add reversed units for division + copy->numerators.insert(copy->numerators.end(), + rhs.denominators.begin(), rhs.denominators.end()); + copy->denominators.insert(copy->denominators.end(), + rhs.numerators.begin(), rhs.numerators.end()); + // Do logical unit cleanup + copy->reduce(); + } + else { + // Only needed if at least two units are used + // Can work directly if both sides are equal + Number left(this), right(rhs); + left.reduce(); right.reduce(); + // Get the necessary conversion factor + double f(right.getUnitConversionFactor(left)); + // Returns zero on incompatible units + if (f == 0.0) { + callStackFrame csf(logger, pstate); + throw Exception::UnitMismatch( + logger, left, right); + } + // Now apply the conversion factor + copy->value(op(lval, right.value() * f)); + } - 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); } + copy->pstate(pstate); + //std::cerr << "RESULT " << copy->inspect() << "\n"; + return copy.detach(); + } + // EO operate - bool Color_HSLA::operator< (const Expression& rhs) const + Value* Number::plus(Value* other, Logger& logger, const SourceSpan& pstate) const { - if (auto r = Cast(&rhs)) { - if (h_ < r->h()) return true; - if (h_ > r->h()) return false; - if (s_ < r->s()) return true; - if (s_ > r->s()) return false; - if (l_ < r->l()) return true; - if (l_ > r->l()) return false; - if (a_ < r->a()) return true; - if (a_ > r->a()) return false; - return false; // is equal + if (const Number* nr = other->isaNumber()) { + return operate(add, *nr, logger, pstate); } - // compare/sort by type - return type() < rhs.type(); + if (!other->isaColor()) return Value::plus(other, logger, pstate); + callStackFrame csf(logger, pstate); + throw Exception::SassScriptException( + "Undefined operation \"" + inspect() + + " + " + other->inspect() + "\".", + logger, pstate); } + // EO plus - bool Color_HSLA::operator== (const Expression& rhs) const + Value* Number::minus(Value* other, Logger& logger, const SourceSpan& pstate) const { - if (auto r = Cast(&rhs)) { - return h_ == r->h() && - s_ == r->s() && - l_ == r->l() && - a_ == r->a(); + if (const Number* nr = other->isaNumber()) { + return operate(sub, *nr, logger, pstate); } - return false; + if (!other->isaColor()) return Value::minus(other, logger, pstate); + callStackFrame csf(logger, pstate); + throw Exception::SassScriptException( + "Undefined operation \"" + inspect() + + " - " + other->inspect() + "\".", + logger, pstate); } + // EO minus - size_t Color_HSLA::hash() const + Value* Number::times(Value* other, Logger& logger, const SourceSpan& pstate) const { - if (hash_ == 0) { - hash_ = std::hash()("HSLA"); - hash_combine(hash_, std::hash()(a_)); - hash_combine(hash_, std::hash()(h_)); - hash_combine(hash_, std::hash()(s_)); - hash_combine(hash_, std::hash()(l_)); + if (const Number* nr = other->isaNumber()) { + return operate(mul, *nr, logger, pstate); } - return hash_; + if (!other->isaColor()) return Value::times(other, logger, pstate); + callStackFrame csf(logger, pstate); + throw Exception::SassScriptException( + "Undefined operation \"" + inspect() + + " * " + other->inspect() + "\".", + logger, pstate); } + // EO times - // hue to RGB helper function - double h_to_rgb(double m1, double m2, double h) + Value* Number::modulo(Value* other, Logger& logger, const SourceSpan& pstate) const { - h = absmod(h, 1.0); - if (h*6.0 < 1) return m1 + (m2 - m1)*h*6; - if (h*2.0 < 1) return m2; - if (h*3.0 < 2) return m1 + (m2 - m1) * (2.0/3.0 - h)*6; - return m1; + if (const Number* nr = other->isaNumber()) { + return operate(mod, *nr, logger, pstate); + } + if (!other->isaColor()) return Value::modulo(other, logger, pstate); + callStackFrame csf(logger, pstate); + throw Exception::SassScriptException( + "Undefined operation \"" + inspect() + + " % " + other->inspect() + "\".", + logger, pstate); } + // EO modulo - Color_RGBA* Color_HSLA::copyAsRGBA() const + Value* Number::remainder(Value* other, Logger& logger, const SourceSpan& pstate) const { + if (const Number* nr = other->isaNumber()) { + return operate(rem, *nr, logger, pstate); + } + if (!other->isaColor()) return Value::remainder(other, logger, pstate); + callStackFrame csf(logger, pstate); + throw Exception::SassScriptException( + "Undefined operation \"" + inspect() + + " %% " + other->inspect() + "\".", + logger, pstate); + } + // EO remainder + + Value* Number::dividedBy(Value* other, Logger& logger, const SourceSpan& pstate) const + { + if (const Number* nr = other->isaNumber()) { + if (!nr->hasUnits()) { + double result = value(); + if (double divisor = nr->value()) { + result /= divisor; + } + else { + if ((result < 0) != std::signbit(divisor)) return SASS_MEMORY_NEW( + Number, pstate, -std::numeric_limits::infinity(), this); + else if (result > 0) return SASS_MEMORY_NEW(Number, pstate, + std::numeric_limits::infinity(), this); + else return SASS_MEMORY_NEW(Number, pstate, + std::numeric_limits::quiet_NaN(), this); + } + return SASS_MEMORY_NEW(Number, + pstate, result, this); + } + else { + return operate(div, *nr, logger, pstate); + } + } + return Value::dividedBy(other, logger, pstate); + } + // EO dividedBy - double h = absmod(h_ / 360.0, 1.0); - double s = clip(s_ / 100.0, 0.0, 1.0); - double l = clip(l_ / 100.0, 0.0, 1.0); - - // Algorithm from the CSS3 spec: http://www.w3.org/TR/css3-color/#hsl-color. - double m2; - if (l <= 0.5) m2 = l*(s+1.0); - else m2 = (l+s)-(l*s); - double m1 = (l*2.0)-m2; - // round the results -- consider moving this into the Color constructor - double r = (h_to_rgb(m1, m2, h + 1.0/3.0) * 255.0); - double g = (h_to_rgb(m1, m2, h) * 255.0); - double b = (h_to_rgb(m1, m2, h - 1.0/3.0) * 255.0); + ///////////////////////////////////////////////////////////////////////// + // Implement unary operations for base value class + ///////////////////////////////////////////////////////////////////////// - return SASS_MEMORY_NEW(Color_RGBA, - pstate(), r, g, b, a(), "" - ); + Value* Number::unaryPlus(Logger& logger, const SourceSpan& pstate) const + { + return SASS_MEMORY_COPY(this); } - Color_HSLA* Color_HSLA::copyAsHSLA() const + Value* Number::unaryMinus(Logger& logger, const SourceSpan& pstate) const { - return SASS_MEMORY_COPY(this); + Number* cpy = SASS_MEMORY_COPY(this); + cpy->value(cpy->value() * -1.0); + return cpy; } ///////////////////////////////////////////////////////////////////////// + // Implement number specific assertions ///////////////////////////////////////////////////////////////////////// - Custom_Error::Custom_Error(SourceSpan pstate, sass::string msg) - : Value(pstate), message_(msg) - { concrete_type(C_ERROR); } - - Custom_Error::Custom_Error(const Custom_Error* ptr) - : Value(ptr), message_(ptr->message_) - { concrete_type(C_ERROR); } - - bool Custom_Error::operator< (const Expression& rhs) const + long Number::assertInt(Logger& logger, const sass::string& name) { - if (auto r = Cast(&rhs)) { - return message() < r->message(); + if (fuzzyIsInt(value_, logger.epsilon)) { + return lround(value_); } - // compare/sort by type - return type() < rhs.type(); + SourceSpan span(this->pstate()); + callStackFrame csf(logger, span); + throw Exception::SassScriptException( + inspect() + " is not an int.", + logger, span, name); } - bool Custom_Error::operator== (const Expression& rhs) const + Number* Number::assertUnitless(Logger& logger, const sass::string& name) { - if (auto r = Cast(&rhs)) { - return message() == r->message(); - } - return false; + if (!hasUnits()) return this; + SourceSpan span(this->pstate()); + callStackFrame csf(logger, span); + throw Exception::SassScriptException( + "Expected " + inspect() + " to have no units.", + logger, span, name); } - ///////////////////////////////////////////////////////////////////////// - ///////////////////////////////////////////////////////////////////////// - - Custom_Warning::Custom_Warning(SourceSpan pstate, sass::string msg) - : Value(pstate), message_(msg) - { concrete_type(C_WARNING); } + Number* Number::assertHasUnits(Logger& logger, const sass::string& unit, const sass::string& name) + { + if (hasUnit(unit)) return this; + SourceSpan span(this->pstate()); + callStackFrame csf(logger, span); + throw Exception::SassScriptException( + "Expected " + inspect() + " to have unit \"" + unit + "\".", + logger, span, name); + } - Custom_Warning::Custom_Warning(const Custom_Warning* ptr) - : Value(ptr), message_(ptr->message_) - { concrete_type(C_WARNING); } + Number* Number::assertNoUnits(Logger& logger, const sass::string& name) + { + if (numerators.empty() && denominators.empty()) return this; + SourceSpan span(this->pstate()); + callStackFrame csf(logger, span); + throw Exception::SassScriptException( + "Expected " + inspect() + " to have no units.", + logger, span, name); + } - bool Custom_Warning::operator< (const Expression& rhs) const + double Number::assertRange(double min, double max, const Units& units, Logger& logger, const sass::string& name) const { - if (auto r = Cast(&rhs)) { - return message() < r->message(); + if (!fuzzyCheckRange(value_, min, max, logger.epsilon)) { + sass::sstream msg; + msg << "Expected " << inspect() << " to be within " + << min << units.unit() << " and " + << max << units.unit() << "."; + SourceSpan span(this->pstate()); + callStackFrame csf(logger, span); + throw Exception::SassScriptException( + msg.str(), logger, span, name); + } + return value_; + } + + const Number* Number::checkPercent(Logger& logger, const sass::string& name) const + { + if (!hasUnit("%")) { + sass::sstream msg; + StringVector dif(numerators); + StringVector mul(denominators); + // ToDo: don't report percentage twice!? + for (auto& unit : mul) unit = " * 1" + unit; + for (auto& unit : dif) unit = " / 1" + unit; + sass::string reunit(StringUtils::join(mul, "") + StringUtils::join(dif, "")); + msg << "$" << name << ": Passing a number without unit % (" << inspect() << ") is deprecated." << STRMLF; + msg << "To preserve current behavior: $" << name << reunit << " * 1%" << STRMLF; + auto add = msg.str(); + logger.addDeprecation(add, pstate(), Logger::WARN_NUMBER_PERCENT); } - // compare/sort by type - return type() < rhs.type(); + return this; } - bool Custom_Warning::operator== (const Expression& rhs) const + Number* Number::coerce(Logger& logger, Number& lhs) { - if (auto r = Cast(&rhs)) { - return message() == r->message(); + if (this->Units::operator==(lhs)) return this; + double factor = getUnitConversionFactor(lhs); + if (factor == 0.0) throw Exception::UnitMismatch(logger, lhs, *this); + return SASS_MEMORY_NEW(Number, pstate(), value() * factor, lhs); + } + + double Number::factorToUnits(const Units& units) + { + if (this->Units::operator==(units)) return 1; + return getUnitConversionFactor(units); + } + + ///////////////////////////////////////////////////////////////////////// + // Implement delayed value fetcher + ///////////////////////////////////////////////////////////////////////// + + // The original value may not be returned + // Therefore make sure original is collected + Value* Number::withoutSlash() + { + if (!hasAsSlash()) return this; + // we are the only holder of this item + // therefore should be safe to alter it + if (this->refcount <= 1) { + lhsAsSlash_.clear(); + rhsAsSlash_.clear(); + return this; } - return false; + // Otherwise we need to make a copy first + Number* copy = SASS_MEMORY_COPY(this); + copy->lhsAsSlash_.clear(); + copy->rhsAsSlash_.clear(); + return copy; } ///////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////// - Boolean::Boolean(SourceSpan pstate, bool val) - : Value(pstate), value_(val), - hash_(0) - { concrete_type(BOOLEAN); } + Boolean::Boolean( + const SourceSpan& pstate, + bool value) : + Value(pstate), + value_(value) + {} - Boolean::Boolean(const Boolean* ptr) - : Value(ptr), - value_(ptr->value_), - hash_(ptr->hash_) - { concrete_type(BOOLEAN); } + Boolean::Boolean( + const Boolean* ptr) : + Value(ptr), + value_(ptr->value_) + {} + + ///////////////////////////////////////////////////////////////////////// - bool Boolean::operator< (const Expression& rhs) const + bool Boolean::operator== (const Value& rhs) const { - if (auto r = Cast(&rhs)) { - return (value() < r->value()); + if (auto right = rhs.isaBoolean()) { + return *this == *right; } return false; } - bool Boolean::operator== (const Expression& rhs) const - { - if (auto r = Cast(&rhs)) { - return (value() == r->value()); - } - return false; - } + bool Boolean::operator== (const Boolean& rhs) const + { + return value() == rhs.value(); + } - size_t Boolean::hash() const + size_t Boolean::hash() const { if (hash_ == 0) { - hash_ = std::hash()(value_); + hash_ = boolHasher(value_); } return hash_; } @@ -883,270 +1398,435 @@ namespace Sass { ///////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////// - String::String(SourceSpan pstate, bool delayed) - : Value(pstate, delayed) - { concrete_type(STRING); } - String::String(const String* ptr) - : Value(ptr) - { concrete_type(STRING); } - - ///////////////////////////////////////////////////////////////////////// - ///////////////////////////////////////////////////////////////////////// + // Value constructor + String::String( + const SourceSpan& pstate, + const char* value, + bool hasQuotes) : + Value(pstate), + value_(value), + hasQuotes_(hasQuotes) + {} + + String::String( + const SourceSpan& pstate, + sass::string&& value, + bool hasQuotes) : + Value(pstate), + value_(std::move(value)), + hasQuotes_(hasQuotes) + {} + + String::String(const String* ptr) : + Value(ptr), + value_(ptr->value_), + hasQuotes_(ptr->hasQuotes_) + {} - String_Schema::String_Schema(SourceSpan pstate, size_t size, bool css) - : String(pstate), Vectorized(size), css_(css), hash_(0) - { concrete_type(STRING); } + AstNode* String::simplify(Logger& logger) { + if (hasQuotes_ == false) return this; + callStackFrame csf(logger, pstate_); + throw Exception::SassScriptException(logger, pstate_, + "Quoted string " + inspect() + " can't be used in a calculation."); + } - 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() + bool String::operator== (const Value& rhs) const { - if (!empty()) { - if (String* str = Cast(last())) str->rtrim(); + if (auto right = rhs.isaString()) { + return *this == *right; } + return false; } - bool String_Schema::is_left_interpolant(void) const + bool String::operator== (const String& rhs) const { - return length() && first()->is_left_interpolant(); + return value() == rhs.value(); } - bool String_Schema::is_right_interpolant(void) const + + bool String::isVar() const { - return length() && last()->is_right_interpolant(); + return !hasQuotes_ && value_.size() > 7 && + StringUtils::startsWithIgnoreCase(value_, "var(", 4); } - bool String_Schema::operator< (const Expression& rhs) const + size_t String::hash() 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; + if (hash_ == 0) { + hash_ = stringHasher(value_); } - // compare/sort by type - return type() < rhs.type(); + return hash_; } - bool String_Schema::operator== (const Expression& rhs) const + ///////////////////////////////////////////////////////////////////////// + + Value* String::plus(Value* other, Logger& logger, const SourceSpan& pstate) const { - if (auto r = Cast(&rhs)) { - if (length() != r->length()) return false; - for (size_t i = 0, L = length(); i < L; ++i) { - auto rv = (*r)[i]; - auto lv = (*this)[i]; - if (*lv != *rv) return false; - } - return true; + if (const String* str = other->isaString()) { + sass::string text(value() + str->value()); + return SASS_MEMORY_NEW(String, + pstate, std::move(text), hasQuotes()); } - return false; + sass::string text(value() + other->toCss()); + return SASS_MEMORY_NEW(String, + pstate, std::move(text), hasQuotes()); } - bool String_Schema::has_interpolants() + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + Map::Map( + const SourceSpan& pstate, + Hashed::ordered_map_type&& move) : + Value(pstate), + Hashed(std::move(move)) + {} + + Map::Map(const Map* ptr) : + Value(ptr), + Hashed(*ptr) + {} + + ///////////////////////////////////////////////////////////////////////// + + // Maps are equal if they have the same items + // at the same key, order is not important. + bool Map::operator== (const Value& rhs) const { - for (auto el : elements()) { - if (el->is_interpolant()) return true; + if (const Map* right = rhs.isaMap()) { + return *this == *right; + } + if (const List* right = rhs.isaList()) { + return right->empty() && empty(); } return false; } - size_t String_Schema::hash() const + // Maps are equal if they have the same items + // at the same key, order is not important. + bool Map::operator== (const Map& rhs) const { - if (hash_ == 0) { - for (auto string : elements()) - hash_combine(hash_, string->hash()); + if (size() != rhs.size()) return false; + for (auto kv : elements_) { + auto lv = kv.second; + auto rv = rhs.at(kv.first); + return ObjEqualityFn(lv, rv); } - return hash_; + return true; } - void String_Schema::set_delayed(bool delayed) + size_t Map::hash() const { - is_delayed(delayed); + if (Hashed::hash_ == 0) { + hash_start(Value::hash_, typeid(Map).hash_code()); + hash_combine(Value::hash_, Hashed::hash()); + } + return Value::hash_; } - ///////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////// - String_Constant::String_Constant(SourceSpan pstate, sass::string val, bool css) - : String(pstate), quote_mark_(0), value_(read_css_string(val, css)), hash_(0) - { } - String_Constant::String_Constant(SourceSpan pstate, const char* beg, bool css) - : String(pstate), quote_mark_(0), value_(read_css_string(sass::string(beg), css)), hash_(0) - { } - String_Constant::String_Constant(SourceSpan pstate, const char* beg, const char* end, bool css) - : String(pstate), quote_mark_(0), value_(read_css_string(sass::string(beg, end-beg), css)), hash_(0) - { } - String_Constant::String_Constant(SourceSpan pstate, const Token& tok, bool css) - : String(pstate), quote_mark_(0), value_(read_css_string(sass::string(tok.begin, tok.end), css)), hash_(0) - { } - - 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; + // Search the position of the given value + size_t Map::indexOf(Value* value) + { + if (List* list = value->isaList()) { + if (list->size() == 2) { + Value* key = list->get(0); + Value* val = list->get(1); + size_t idx = 0; + for (auto kv : elements_) { + if (*kv.first == *key) { + if (*kv.second == *val) { + return idx; + } + } + ++idx; + } + } + } + return NPOS; } - bool String_Constant::operator< (const Expression& rhs) const + // Return list with two items (key and value) + Value* Map::getPairAsList(size_t idx) { - if (auto qstr = Cast(&rhs)) { - return value() < qstr->value(); + auto kv = elements_.begin() + idx; + // ToDo: really can't re-use memory? + if (false && itpair->size() == 2) { + itpair->set(0, kv->first); + itpair->set(1, kv->second); } - else if (auto cstr = Cast(&rhs)) { - return value() < cstr->value(); + else { + itpair = SASS_MEMORY_NEW( + List, pstate(), + { kv->first, kv->second }, + SASS_SPACE); } - // compare/sort by type - return type() < rhs.type(); + return itpair.detach(); } - bool String_Constant::operator== (const Expression& rhs) const + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + List::List( + const SourceSpan& pstate, + const ValueVector& values, + SassSeparator separator, + bool hasBrackets) : + Value(pstate), + Vectorized(values), + separator_(separator), + hasBrackets_(hasBrackets) + {} + + List::List(const SourceSpan& pstate, + ValueVector&& values, + SassSeparator separator, + bool hasBrackets) : + Value(pstate), + Vectorized(std::move(values)), + separator_(separator), + hasBrackets_(hasBrackets) + {} + + List::List( + const List* ptr) : + Value(ptr), + Vectorized(ptr), + separator_(ptr->separator_), + hasBrackets_(ptr->hasBrackets_) + {} + + ///////////////////////////////////////////////////////////////////////// + + bool List::operator==(const Value& rhs) const { - if (auto qstr = Cast(&rhs)) { - return value() == qstr->value(); + if (const List* right = rhs.isaList()) { + return *this == *right; } - else if (auto cstr = Cast(&rhs)) { - return value() == cstr->value(); + if (const Map* right = rhs.isaMap()) { + return empty() && right->empty(); } return false; } - sass::string String_Constant::inspect() const + bool List::operator==(const List& rhs) const { - return quote(value_, '*'); + if (size() != rhs.size()) return false; + if (separator() != rhs.separator()) return false; + if (hasBrackets() != rhs.hasBrackets()) return false; + for (size_t i = 0, L = size(); i < L; ++i) { + auto rv = rhs.get(i); + auto lv = this->get(i); + if (!lv && rv) return false; + else if (!rv && lv) return false; + else if (!(*lv == *rv)) return false; + } + return true; } - void String_Constant::rtrim() + size_t List::hash() const { - str_rtrim(value_); + if (Vectorized::hash_ == 0) { + hash_start(Value::hash_, typeid(List).hash_code()); + hash_combine(Value::hash_, Vectorized::hash()); + hash_combine(Value::hash_, sizetHasher(separator())); + hash_combine(Value::hash_, boolHasher(hasBrackets())); + } + return Value::hash_; } - size_t String_Constant::hash() const + ///////////////////////////////////////////////////////////////////////// + + Map* List::assertMap(Logger& logger, const sass::string& name) { - if (hash_ == 0) { - hash_ = std::hash()(value_); - } - return hash_; + if (!empty()) { return Value::assertMap(logger, name); } + else { return SASS_MEMORY_NEW(Map, pstate()); } } ///////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////// - String_Quoted::String_Quoted(SourceSpan pstate, sass::string val, char q, - bool keep_utf8_escapes, bool skip_unquoting, - bool strict_unquoting, bool css) - : String_Constant(pstate, val, css) + ArgumentList::ArgumentList( + const SourceSpan& pstate, + SassSeparator separator, + const ValueVector& values, + const ValueFlatMap& keywords) : + List(pstate, + values, + separator, + false), + _keywords(keywords), + _wereKeywordsAccessed(false) + {} + + ArgumentList::ArgumentList( + const SourceSpan& pstate, + SassSeparator separator, + ValueVector&& values, + ValueFlatMap&& keywords) : + List(pstate, + std::move(values), + separator, + false), + _keywords(std::move(keywords)), + _wereKeywordsAccessed(false) + {} + + ArgumentList::ArgumentList( + const ArgumentList* ptr) : + List(ptr), + _keywords(ptr->_keywords), + _wereKeywordsAccessed(ptr->_wereKeywordsAccessed) + {} + + ///////////////////////////////////////////////////////////////////////// + + bool ArgumentList::operator==(const Value& rhs) const { - if (skip_unquoting == false) { - value_ = unquote(value_, "e_mark_, keep_utf8_escapes, strict_unquoting); + if (const ArgumentList* right = rhs.isaArgumentList()) { + return *this == *right; } - if (q && quote_mark_) quote_mark_ = q; + return List::operator==(rhs); } - String_Quoted::String_Quoted(const String_Quoted* ptr) - : String_Constant(ptr) - { } + bool ArgumentList::operator==(const ArgumentList& rhs) const + { + return _keywords == rhs._keywords; + } - bool String_Quoted::operator< (const Expression& rhs) const + size_t ArgumentList::hash() const { - if (auto qstr = Cast(&rhs)) { - return value() < qstr->value(); - } - else if (auto cstr = Cast(&rhs)) { - return value() < cstr->value(); + if (Vectorized::hash_ == 0) { + hash_start(Value::hash_, typeid(ArgumentList).hash_code()); + hash_combine(Value::hash_, Vectorized::hash()); + for (auto child : _keywords) { + hash_combine(Value::hash_, child.first.hash()); + hash_combine(Value::hash_, child.second->hash()); + } } - // compare/sort by type - return type() < rhs.type(); + return Value::hash_; } - bool String_Quoted::operator== (const Expression& rhs) const + ///////////////////////////////////////////////////////////////////////// + + // Convert native string keys to sass strings + Map* ArgumentList::keywordsAsSassMap() const { - if (auto qstr = Cast(&rhs)) { - return value() == qstr->value(); + Map* map = SASS_MEMORY_NEW(Map, pstate()); + for (auto kv : _keywords) { + String* keystr = SASS_MEMORY_NEW( + String, kv.second->pstate(), + sass::string(kv.first.orig())); + map->insert(keystr, kv.second); } - else if (auto cstr = Cast(&rhs)) { - return value() == cstr->value(); + return map; + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + Function::Function( + const SourceSpan& pstate, + CallableObj callable) : + Value(pstate), + callable_(callable) + {} + + Function::Function( + const SourceSpan& pstate, + const sass::string& cssName) : + Value(pstate), + cssName_(cssName) + {} + + Function::Function(const Function* ptr) : + Value(ptr), + callable_(ptr->callable_) + {} + + ///////////////////////////////////////////////////////////////////////// + + bool Function::operator== (const Value& rhs) const + { + if (const Function* fn = rhs.isaFunction()) { + return *this == *fn; } return false; } - sass::string String_Quoted::inspect() const + bool Function::operator== (const Function& rhs) const { - return quote(value_, '*'); + return ObjEqualityFn(callable_, rhs.callable()); } ///////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////// - Null::Null(SourceSpan pstate) - : Value(pstate) - { concrete_type(NULL_VAL); } - - Null::Null(const Null* ptr) : Value(ptr) - { concrete_type(NULL_VAL); } + CalcOperation::CalcOperation( + const SourceSpan& pstate, + const SassOperator op, + AstNode* left, + AstNode* right) : + Value(pstate), + op_(op), + left_(left), + right_(right) + {} - bool Null::operator< (const Expression& rhs) const + CalcOperation::CalcOperation( + const CalcOperation * ptr) : + Value(ptr), + op_(ptr->op()), + left_(ptr->left()), + right_(ptr->right()) { - if (Cast(&rhs)) { - return false; - } - // compare/sort by type - return type() < rhs.type(); } - bool Null::operator== (const Expression& rhs) const + size_t CalcOperation::hash() const { - return Cast(&rhs) != nullptr; + return 123; } - size_t Null::hash() const + bool CalcOperation::operator==(const Value& rhs) const { - return -1; + return false; } ///////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////// - Parent_Reference::Parent_Reference(SourceSpan pstate) - : Value(pstate) - { concrete_type(PARENT); } + Mixin::Mixin( + const SourceSpan& pstate, + Callable* callable) : + Value(pstate), + callable_(callable) + {} - Parent_Reference::Parent_Reference(const Parent_Reference* ptr) - : Value(ptr) - { concrete_type(PARENT); } + Mixin::Mixin( + const Mixin * ptr) : + Value(ptr), + callable_(ptr->callable()) + {} - ///////////////////////////////////////////////////////////////////////// - ///////////////////////////////////////////////////////////////////////// + size_t Mixin::hash() const + { + return callable_->hash(); + } - IMPLEMENT_AST_OPERATORS(List); - IMPLEMENT_AST_OPERATORS(Map); - IMPLEMENT_AST_OPERATORS(Binary_Expression); - IMPLEMENT_AST_OPERATORS(Function); - IMPLEMENT_AST_OPERATORS(Function_Call); - IMPLEMENT_AST_OPERATORS(Variable); - IMPLEMENT_AST_OPERATORS(Number); - IMPLEMENT_AST_OPERATORS(Color_RGBA); - IMPLEMENT_AST_OPERATORS(Color_HSLA); - IMPLEMENT_AST_OPERATORS(Custom_Error); - IMPLEMENT_AST_OPERATORS(Custom_Warning); - IMPLEMENT_AST_OPERATORS(Boolean); - IMPLEMENT_AST_OPERATORS(String_Schema); - IMPLEMENT_AST_OPERATORS(String_Constant); - IMPLEMENT_AST_OPERATORS(String_Quoted); - IMPLEMENT_AST_OPERATORS(Null); - IMPLEMENT_AST_OPERATORS(Parent_Reference); + bool Mixin::operator==(const Value& rhs) const + { + if (const Mixin* mixin = rhs.isaMixin()) { + return *this == *mixin; + } + return false; + } + + bool Mixin::operator== (const Mixin& rhs) const + { + return ObjEqualityFn(callable_, rhs.callable()); + } ///////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////// diff --git a/src/ast_values.hpp b/src/ast_values.hpp index 6a240bfb5a..8c16c14a87 100644 --- a/src/ast_values.hpp +++ b/src/ast_values.hpp @@ -1,498 +1,1104 @@ -#ifndef SASS_AST_VALUES_H -#define SASS_AST_VALUES_H +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#ifndef SASS_AST_VALUES_HPP +#define SASS_AST_VALUES_HPP -// sass.hpp must go before all system headers to get the -// __EXTENSIONS__ fix on Solaris. -#include "sass.hpp" -#include "ast.hpp" +// sass.hpp must go before all system headers +// to get the __EXTENSIONS__ fix on Solaris. +#include "capi_sass.hpp" + +#include "units.hpp" +#include "ast_nodes.hpp" +#include "ast_callables.hpp" namespace Sass { - ////////////////////////////////////////////////////////////////////// - // Still just an expression, but with a to_string method - ////////////////////////////////////////////////////////////////////// - class PreValue : public Expression { + class Compiler; + + ///////////////////////////////////////////////////////////////////////// + // Errors from Sass_Values. + ///////////////////////////////////////////////////////////////////////// + + class CustomError final : public Value + { + private: + + ADD_CONSTREF(sass::string, message) + public: - PreValue(SourceSpan pstate, bool d = false, bool e = false, bool i = false, Type ct = NONE); - ATTACH_VIRTUAL_AST_OPERATIONS(PreValue); - virtual ~PreValue() { } + + // Value constructor + CustomError( + const SourceSpan& pstate, + const sass::string& message); + + // Copy constructor + CustomError(const CustomError* ptr); + + // Implement interface for base Value class + size_t hash() const override final { return 0; } + enum SassValueType getTag() const override final { return SASS_ERROR; } + const sass::string& type() const override final { return Strings::error; } + + // Implement equality comparators for base value class + bool operator== (const Value& rhs) const override final; + // Implement same class compare operator + bool operator== (const CustomError& rhs) const; + + // Main entry point for Value Visitor pattern + void accept(ValueVisitor* visitor) override final; + Value* accept(ValueVisitor* visitor) override final; + + // Copy operations for childless items + CustomError* copy(SASS_MEMORY_ARGS bool childless) const override final { + return SASS_MEMORY_NEW_DBG(CustomError, this); + } + + IMPLEMENT_ISA_CASTER(CustomError); }; - ////////////////////////////////////////////////////////////////////// - // base class for values that support operations - ////////////////////////////////////////////////////////////////////// - class Value : public PreValue { + ///////////////////////////////////////////////////////////////////////// + // Warnings from Sass_Values. + ///////////////////////////////////////////////////////////////////////// + + class CustomWarning final : public Value + { + private: + + ADD_CONSTREF(sass::string, message) + public: - Value(SourceSpan pstate, bool d = false, bool e = false, bool i = false, Type ct = NONE); - // Some obects are not meant to be compared - // ToDo: maybe fallback to pointer comparison? - virtual bool operator< (const Expression& rhs) const override = 0; - virtual bool operator== (const Expression& rhs) const override = 0; + // Value constructor + CustomWarning( + const SourceSpan& pstate, + const sass::string& message); - // We can give some reasonable implementations by using - // inverst operators on the specialized implementations - virtual bool operator> (const Expression& rhs) const { - return rhs < *this; - } - virtual bool operator!= (const Expression& rhs) const { - return !(*this == rhs); - } + // Copy constructor + CustomWarning(const CustomWarning* ptr); - ATTACH_VIRTUAL_AST_OPERATIONS(Value); + // Implement interface for base Value class + size_t hash() const override final { return 0; } + enum SassValueType getTag() const override final { return SASS_WARNING; } + const sass::string& type() const override final { return Strings::warning; } + + // Implement equality comparators for base value class + bool operator== (const Value& rhs) const override final; + // Implement same class compare operator + bool operator== (const CustomWarning& rhs) const; + + // Main entry point for Value Visitor pattern + void accept(ValueVisitor* visitor) override final; + Value* accept(ValueVisitor* visitor) override final; + + // Copy operations for childless items + CustomWarning* copy(SASS_MEMORY_ARGS bool childless) const override final { + return SASS_MEMORY_NEW_DBG(CustomWarning, this); + } }; /////////////////////////////////////////////////////////////////////// - // Lists of values, both comma- and space-separated (distinguished by a - // type-tag.) Also used to represent variable-length argument lists. + // The null value. /////////////////////////////////////////////////////////////////////// - class List : public Value, public Vectorized { - void adjust_after_pushing(ExpressionObj 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) + + class Null final : public Value + { public: - List(SourceSpan pstate, size_t size = 0, enum Sass_Separator sep = SASS_SPACE, bool argl = false, bool bracket = false); - sass::string type() const override { return is_arglist_ ? "arglist" : "list"; } - static sass::string type_name() { return "list"; } - const char* sep_string(bool compressed = false) const { - return separator() == SASS_SPACE ? - " " : (compressed ? "," : ", "); - } - bool is_invisible() const override { return empty() && !is_bracketed(); } - ExpressionObj value_at_index(size_t i); - virtual size_t hash() const override; - virtual size_t size() const; - virtual void set_delayed(bool delayed) override; + // Value constructor + Null(const SourceSpan& pstate); + + // Copy constructor + Null(const Null* ptr); + + // Implement simple checkers for base value class + bool isNull() const override final { return true; } + bool isBlank() const override final { return true; } + bool isTruthy() const override final { return false; } - virtual bool operator< (const Expression& rhs) const override; - virtual bool operator== (const Expression& rhs) const override; + // Implement interface for base Value class + size_t hash() const override final; - ATTACH_AST_OPERATIONS(List) - ATTACH_CRTP_PERFORM_METHODS() + enum SassValueType getTag() const override final { return SASS_NULL; } + const sass::string& type() const override final { return Strings::null; } + + // Implement equality comparators for base value class + bool operator== (const Value& rhs) const override final; + + // Main entry point for Value Visitor pattern + void accept(ValueVisitor* visitor) override final { + return visitor->visitNull(this); + } + Value* accept(ValueVisitor* visitor) override final { + return visitor->visitNull(this); + } + + // Copy operations for childless items + Null* copy(SASS_MEMORY_ARGS bool childless) const override final { + return SASS_MEMORY_NEW_DBG(Null, this); + } + + IMPLEMENT_ISA_CASTER(Null); }; /////////////////////////////////////////////////////////////////////// - // Key value paris. + // Base class for colors (either rgba or hsla). /////////////////////////////////////////////////////////////////////// - class Map : public Value, public Hashed { - void adjust_after_pushing(std::pair p) override { is_expanded(false); } + + class Color : public Value + { + private: + + ADD_CONSTREF(sass::string, disp); + ADD_CONSTREF(double, a); + ADD_CONSTREF(bool, parsed); + public: - Map(SourceSpan pstate, size_t size = 0); - sass::string type() const override { return "map"; } - static sass::string type_name() { return "map"; } - bool is_invisible() const override { return empty(); } - List_Obj to_list(SourceSpan& pstate); - virtual size_t hash() const override; + // Value constructor + Color(const SourceSpan& pstate, + double alpha = 1, + const sass::string& disp = "", + bool parsed = false); + + // Copy constructor + Color(const Color* ptr); + + // Convert and copy only if necessary + virtual ColorRgba* toRGBA() const = 0; + virtual ColorHsla* toHSLA() const = 0; + virtual ColorHwba* toHWBA() const = 0; + // Convert if necessary and return a copy + virtual ColorRgba* copyAsRGBA() const = 0; + virtual ColorHsla* copyAsHSLA() const = 0; + virtual ColorHwba* copyAsHWBA() const = 0; + + // Implement interface for base Value class + virtual size_t hash() const override = 0; + enum SassValueType getTag() const override final { return SASS_COLOR; } + const sass::string& type() const override final { return Strings::color; } + + // Implement some operations for base value class + Value* plus(Value* other, Logger& logger, const SourceSpan& pstate) const override final; + Value* minus(Value* other, Logger& logger, const SourceSpan& pstate) const override final; + Value* dividedBy(Value* other, Logger& logger, const SourceSpan& pstate) const override final; + Value* modulo(Value* other, Logger& logger, const SourceSpan& pstate) const override final; + Value* remainder(Value* other, Logger& logger, const SourceSpan& pstate) const override final; + + // Implement type fetcher for base value class (throws in base implementation) + const Color* assertColor(Logger& logger, const sass::string& name = Strings::empty) const override final { return this; } + + // Main entry point for Value Visitor pattern + void accept(ValueVisitor* visitor) override final { + return visitor->visitColor(this); + } + Value* accept(ValueVisitor* visitor) override final { + return visitor->visitColor(this); + } - virtual bool operator< (const Expression& rhs) const override; - virtual bool operator== (const Expression& rhs) const override; + // This is a very interesting line, as it seems pointless, since the base class + // already marks this as an unimplemented interface methods, but by defining this + // line here, we make sure that callers know the return is a bit more specific. + virtual Color* copy(SASS_MEMORY_ARGS bool childless = false) const override = 0; - ATTACH_AST_OPERATIONS(Map) - ATTACH_CRTP_PERFORM_METHODS() + IMPLEMENT_ISA_CASTER(Color); }; + /////////////////////////////////////////////////////////////////////// + // A sass color in RGBA representation. + /////////////////////////////////////////////////////////////////////// - ////////////////////////////////////////////////////////////////////////// - // Binary expressions. Represents logical, relational, and arithmetic - // operations. Templatized to avoid large switch statements and repetitive - // subclassing. - ////////////////////////////////////////////////////////////////////////// - class Binary_Expression : public PreValue { + class ColorRgba final : public Color + { private: - HASH_PROPERTY(Operand, op) - HASH_PROPERTY(ExpressionObj, left) - HASH_PROPERTY(ExpressionObj, right) - mutable size_t hash_; - public: - Binary_Expression(SourceSpan pstate, - Operand op, ExpressionObj lhs, ExpressionObj rhs); - const sass::string type_name(); - const sass::string separator(); - bool is_left_interpolant(void) const override; - bool is_right_interpolant(void) const override; - bool has_interpolant() const override; + ADD_CONSTREF(double, r); + ADD_CONSTREF(double, g); + ADD_CONSTREF(double, b); - virtual void set_delayed(bool delayed) override; + public: - virtual bool operator< (const Expression& rhs) const override; - virtual bool operator==(const Expression& rhs) const override; + // Value constructor + ColorRgba(const SourceSpan& pstate, + double red, double green, double blue, double alpha = 1.0, + const sass::string& disp = "", + bool parsed = false); + + // Copy constructor + ColorRgba(const ColorRgba* ptr); + + // Convert and copy only if necessary + ColorRgba* toRGBA() const override final; + ColorHsla* toHSLA() const override final; + ColorHwba* toHWBA() const override final; + // Convert if necessary and return a copy + ColorRgba* copyAsRGBA() const override final; + ColorHsla* copyAsHSLA() const override final; + ColorHwba* copyAsHWBA() const override final; + + // Implement interface for base color class + size_t hash() const override final; + + // Implement equality comparators for base value class + bool operator== (const Value& rhs) const override final; + // Implement same class compare operator + bool operator== (const ColorRgba& rhs) const; + + // Copy operations for childless items + ColorRgba* copy(SASS_MEMORY_ARGS bool childless) const override final { + return SASS_MEMORY_NEW_DBG(ColorRgba, this); + } - virtual size_t hash() const override; - enum Sass_OP optype() const { return op_.operand; } - ATTACH_AST_OPERATIONS(Binary_Expression) - ATTACH_CRTP_PERFORM_METHODS() + IMPLEMENT_ISA_CASTER(ColorRgba); }; - //////////////////////////////////////////////////// - // Function reference. - //////////////////////////////////////////////////// - class Function final : public Value { - public: - ADD_PROPERTY(Definition_Obj, definition) - ADD_PROPERTY(bool, is_css) - public: - Function(SourceSpan pstate, Definition_Obj def, bool css); - - sass::string type() const override { return "function"; } - static sass::string type_name() { return "function"; } - bool is_invisible() const override { return true; } - sass::string name(); + /////////////////////////////////////////////////////////////////////// + // A sass color in HSLA representation. + /////////////////////////////////////////////////////////////////////// - bool operator< (const Expression& rhs) const override; - bool operator== (const Expression& rhs) const override; + class ColorHsla final : public Color + { + private: - ATTACH_AST_OPERATIONS(Function) - ATTACH_CRTP_PERFORM_METHODS() - }; + ADD_CONSTREF(double, h); + ADD_CONSTREF(double, s); + ADD_CONSTREF(double, l); - ////////////////// - // 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(SourceSpan pstate, sass::string n, Arguments_Obj args, void* cookie); - Function_Call(SourceSpan pstate, sass::string n, Arguments_Obj args, Function_Obj func); - Function_Call(SourceSpan pstate, sass::string n, Arguments_Obj args); - Function_Call(SourceSpan pstate, String_Obj n, Arguments_Obj args, void* cookie); - Function_Call(SourceSpan pstate, String_Obj n, Arguments_Obj args, Function_Obj func); - Function_Call(SourceSpan pstate, String_Obj n, Arguments_Obj args); + // Value constructor + ColorHsla(const SourceSpan& pstate, + double hue, double saturation, double lightness, double alpha = 1, + const sass::string& disp = "", + bool parsed = false); + + // Copy constructor + ColorHsla(const ColorHsla* ptr); + + // Convert and copy only if necessary + ColorRgba* toRGBA() const override final; + ColorHsla* toHSLA() const override final; + ColorHwba* toHWBA() const override final; + // Convert if necessary and return a copy + ColorRgba* copyAsRGBA() const override final; + ColorHsla* copyAsHSLA() const override final; + ColorHwba* copyAsHWBA() const override final; + + // Implement interface for base color class + size_t hash() const override final; + + // Implement equality comparators for base value class + bool operator== (const Value& rhs) const override final; + // Implement same class compare operator + bool operator== (const ColorHsla& rhs) const; + + // Copy operations for childless items + ColorHsla* copy(SASS_MEMORY_ARGS bool childless) const override final { + return SASS_MEMORY_NEW_DBG(ColorHsla, this); + } - sass::string name() const; - bool is_css(); + IMPLEMENT_ISA_CASTER(ColorHsla); + }; - bool operator==(const Expression& rhs) const override; + /////////////////////////////////////////////////////////////////////// + // A sass color in HSLA representation. + /////////////////////////////////////////////////////////////////////// - size_t hash() const override; + class ColorHwba final : public Color + { + private: - ATTACH_AST_OPERATIONS(Function_Call) - ATTACH_CRTP_PERFORM_METHODS() - }; + ADD_CONSTREF(double, h); + ADD_CONSTREF(double, w); + ADD_CONSTREF(double, b); - /////////////////////// - // Variable references. - /////////////////////// - class Variable final : public PreValue { - ADD_CONSTREF(sass::string, name) public: - Variable(SourceSpan pstate, sass::string n); - virtual bool operator==(const Expression& rhs) const override; - virtual size_t hash() const override; - ATTACH_AST_OPERATIONS(Variable) - ATTACH_CRTP_PERFORM_METHODS() + + // Value constructor + ColorHwba(const SourceSpan& pstate, + double hue, double whiteness, double blackness, double alpha = 1, + const sass::string& disp = "", + bool parsed = false); + + // Copy constructor + ColorHwba(const ColorHwba* ptr); + + // Convert and copy only if necessary + ColorRgba* toRGBA() const override final; + ColorHsla* toHSLA() const override final; + ColorHwba* toHWBA() const override final; + // Convert if necessary and return a copy + ColorRgba* copyAsRGBA() const override final; + ColorHsla* copyAsHSLA() const override final; + ColorHwba* copyAsHWBA() const override final; + + // Implement interface for base color class + size_t hash() const override final; + + // Implement equality comparators for base value class + bool operator== (const Value& rhs) const override final; + // Implement same class compare operator + bool operator== (const ColorHwba& rhs) const; + + // Copy operations for childless items + ColorHwba* copy(SASS_MEMORY_ARGS bool childless) const override final { + return SASS_MEMORY_NEW_DBG(ColorHwba, this); + } + + IMPLEMENT_ISA_CASTER(ColorHwba); }; - //////////////////////////////////////////////// - // Numbers, percentages, dimensions, and colors. - //////////////////////////////////////////////// - class Number final : public Value, public Units { - HASH_PROPERTY(double, value) - ADD_PROPERTY(bool, zero) - mutable size_t hash_; + /////////////////////////////////////////////////////////////////////// + // A sass number with optional units + /////////////////////////////////////////////////////////////////////// + + class Number final : public Value, public Units, public CalcItem + { + private: + + ADD_CONSTREF(double, value); + // The representation of this number as two + // slash-separated numbers, if it has one. + ADD_CONSTREF(NumberObj, lhsAsSlash); + ADD_CONSTREF(NumberObj, rhsAsSlash); + public: - Number(SourceSpan pstate, double val, sass::string u = "", bool zero = true); - bool zero() { return zero_; } + // Value constructor + Number( + const SourceSpan& pstate, + double value = 0.0, + const sass::string& units = ""); + + // Value constructor + Number( + const SourceSpan& pstate, + double value, Units units); + + // Copy constructor + Number(const Number* ptr); - sass::string type() const override { return "number"; } - static sass::string type_name() { return "number"; } + // Numbers can't be simplified further + AstNode* simplify(Logger& logger) override final { return this; } + + // Check if we have delayed value info + bool hasAsSlash() { + return !lhsAsSlash_.isNull() + && !rhsAsSlash_.isNull(); + } + + // Check if number matches [unit] + bool hasUnit(const sass::string& unit) const { + return numerators.size() == 1 && + denominators.empty() && + numerators.front() == unit; + } // cancel out unnecessary units // result will be in input units - void reduce(); + void reduce() + { + // apply conversion factor + value_ *= this->Units::reduce(); + } // normalize units to defaults // needed to compare two numbers - void normalize(); - - size_t hash() const override; + void normalize() + { + // apply conversion factor + value_ *= this->Units::normalize(); + } - bool operator< (const Number& rhs) const; + Number* coerce(Logger& logger, Number& rhs); + double factorToUnits(const Units& units); + + // Implement delayed value fetcher + Value* withoutSlash() override final; + + // Implement interface for base Value class + size_t hash() const override final; + enum SassValueType getTag() const override final { return SASS_NUMBER; } + const sass::string& type() const override final { return Strings::number; } + + // Implement some comparators for base value class + bool greaterThan(Value* other, Logger& logger, const SourceSpan& pstate) const override final; + bool greaterThanOrEquals(Value* other, Logger& logger, const SourceSpan& pstate) const override final; + bool lessThan(Value* other, Logger& logger, const SourceSpan& pstate) const override final; + bool lessThanOrEquals(Value* other, Logger& logger, const SourceSpan& pstate) const override final; + + // Implement some operations for base value class + Value* plus(Value* other, Logger& logger, const SourceSpan& pstate) const override final; + Value* minus(Value* other, Logger& logger, const SourceSpan& pstate) const override final; + Value* times(Value* other, Logger& logger, const SourceSpan& pstate) const override final; + Value* modulo(Value* other, Logger& logger, const SourceSpan& pstate) const override final; + Value* remainder(Value* other, Logger& logger, const SourceSpan& pstate) const override final; + Value* dividedBy(Value* other, Logger& logger, const SourceSpan& pstate) const override final; + + // Implement unary operations for base value class + Value* unaryPlus(Logger& logger, const SourceSpan& pstate) const override final; + Value* unaryMinus(Logger& logger, const SourceSpan& pstate) const override final; + + // Implement type fetcher for base value class (throws in base implementation) + Number* assertNumber(Logger& logger, const sass::string& name = Strings::empty) override final { return this; } + + // Implement number specific assertions + long assertInt(Logger& logger, const sass::string& name = Strings::empty); + Number* assertUnitless(Logger& logger, const sass::string& name = Strings::empty); + Number* assertHasUnits(Logger& logger, const sass::string& unit, const sass::string& name = Strings::empty); + Number* assertNoUnits(Logger& logger, const sass::string& name = Strings::empty); + double assertRange(double min, double max, const Units& units, Logger& logger, const sass::string& name = Strings::empty) const; + + const Number* checkPercent(Logger& logger, const sass::string& name) const; + + // Implement equality comparators for base value class + bool operator== (const Value& rhs) const override final; + // Implement same class compare operator bool operator== (const Number& rhs) const; - bool operator< (const Expression& rhs) const override; - bool operator== (const Expression& rhs) const override; - ATTACH_AST_OPERATIONS(Number) - ATTACH_CRTP_PERFORM_METHODS() + + // Main entry point for Value Visitor pattern + void accept(ValueVisitor* visitor) override final { + return visitor->visitNumber(this); + } + Value* accept(ValueVisitor* visitor) override final { + return visitor->visitNumber(this); + } + + // Copy operations for childless items + Number* copy(SASS_MEMORY_ARGS bool childless) const override final { + return SASS_MEMORY_NEW_DBG(Number, this); + } + + private: + + Value* operate(double (*op)(double, double), const Number& rhs, Logger& logger, const SourceSpan& pstate) const; + + IMPLEMENT_ISA_CASTER(Number); }; - ////////// - // Colors. - ////////// - class Color : public Value { - ADD_CONSTREF(sass::string, disp) - HASH_PROPERTY(double, a) - protected: - mutable size_t hash_; + /////////////////////////////////////////////////////////////////////// + // A sass boolean (either true or false) + /////////////////////////////////////////////////////////////////////// + + class Boolean final : public Value + { + private: + + ADD_CONSTREF(bool, value) + public: - Color(SourceSpan pstate, double a = 1, const sass::string disp = ""); - sass::string type() const override { return "color"; } - static sass::string type_name() { return "color"; } + // Value constructor + Boolean( + const SourceSpan& pstate, + bool value = false); - virtual size_t hash() const override = 0; + // Copy constructor + Boolean(const Boolean* ptr); + + // Implement simple checkers for base value class + bool isTruthy() const override final { return value_; } - bool operator< (const Expression& rhs) const override; - bool operator== (const Expression& rhs) const override; + // Implement interface for base Value class + size_t hash() const override final; + enum SassValueType getTag() const override final { return SASS_BOOLEAN; } + const sass::string& type() const override final { return Strings::boolean; } - virtual Color_RGBA* copyAsRGBA() const = 0; - virtual Color_RGBA* toRGBA() = 0; + // Implement equality comparators for base value class + bool operator== (const Value& rhs) const override final; + // Implement same class compare operator + bool operator== (const Boolean& rhs) const; - virtual Color_HSLA* copyAsHSLA() const = 0; - virtual Color_HSLA* toHSLA() = 0; + // Main entry point for Value Visitor pattern + void accept(ValueVisitor* visitor) override final { + return visitor->visitBoolean(this); + } + Value* accept(ValueVisitor* visitor) override final { + return visitor->visitBoolean(this); + } - ATTACH_VIRTUAL_AST_OPERATIONS(Color) + // Copy operations for childless items + Boolean* copy(SASS_MEMORY_ARGS bool childless) const override final { + return SASS_MEMORY_NEW_DBG(Boolean, this); + } + + IMPLEMENT_ISA_CASTER(Boolean); }; - ////////// - // Colors. - ////////// - class Color_RGBA final : public Color { - HASH_PROPERTY(double, r) - HASH_PROPERTY(double, g) - HASH_PROPERTY(double, b) + /////////////////////////////////////////////////////////////////////// + // A sass string (optionally quoted on rendering) + /////////////////////////////////////////////////////////////////////// + class String final : public Value, public CalcItem + { + private: + + ADD_CONSTREF(sass::string, value); + ADD_CONSTREF(bool, hasQuotes); + public: - Color_RGBA(SourceSpan pstate, double r, double g, double b, double a = 1, const sass::string disp = ""); - sass::string type() const override { return "color"; } - static sass::string type_name() { return "color"; } + // Value constructor + String( + const SourceSpan& pstate, + const char* value, + bool hasQuotes = false); + + String( + const SourceSpan& pstate, + sass::string&& value, + bool hasQuotes = false); + + // Copy constructor + String(const String* ptr); - size_t hash() const override; + AstNode* simplify(Logger& logger) override final; - Color_RGBA* copyAsRGBA() const override; - Color_RGBA* toRGBA() override { return this; } + // Check if value would render empty + bool isBlank() const override final { + if (hasQuotes_) return false; + return value_.empty(); + } + + bool isVar() const; + + // Implement interface for base Value class + size_t hash() const override final; + enum SassValueType getTag() const override final { return SASS_STRING; } + const sass::string& type() const override { return Strings::string; } + + // Implement equality comparators for base value class + bool operator== (const Value& rhs) const override final; + // Implement same class compare operator + bool operator== (const String& rhs) const; + + // Implement type fetcher for base value class (throws in base implementation) + String* assertString(Logger& logger, const sass::string& name = Strings::empty) override final { return this; } - Color_HSLA* copyAsHSLA() const override; - Color_HSLA* toHSLA() override { return copyAsHSLA(); } + // Implement some operations for base value class + Value* plus(Value* other, Logger& logger, const SourceSpan& pstate) const override final; - bool operator< (const Expression& rhs) const override; - bool operator== (const Expression& rhs) const override; + // Main entry point for Value Visitor pattern + // Main entry point for Value Visitor pattern + void accept(ValueVisitor* visitor) override final { + return visitor->visitString(this); + } + Value* accept(ValueVisitor* visitor) override final { + return visitor->visitString(this); + } + + // Copy operations for childless items + String* copy(SASS_MEMORY_ARGS bool childless) const override final { + return SASS_MEMORY_NEW_DBG(String, this); + } - ATTACH_AST_OPERATIONS(Color_RGBA) - ATTACH_CRTP_PERFORM_METHODS() + IMPLEMENT_ISA_CASTER(String); }; + /////////////////////////////////////////////////////////////////////// + // A sass map (which keeps the insertion order) + /////////////////////////////////////////////////////////////////////// + class Map final : public Value, public Hashed + { + private: + + // Helper for getPairAsList to avoid memory leaks + // Returned by `Values::iterator::operator*()` + ListObj itpair; - ////////// - // Colors. - ////////// - class Color_HSLA final : public Color { - HASH_PROPERTY(double, h) - HASH_PROPERTY(double, s) - HASH_PROPERTY(double, l) public: - Color_HSLA(SourceSpan pstate, double h, double s, double l, double a = 1, const sass::string disp = ""); - sass::string type() const override { return "color"; } - static sass::string type_name() { return "color"; } + // Value constructor + Map( + const SourceSpan& pstate, + Hashed::ordered_map_type&& move = {}); - size_t hash() const override; + // Copy constructor + Map(const Map* ptr); - Color_RGBA* copyAsRGBA() const override; - Color_RGBA* toRGBA() override { return copyAsRGBA(); } + // Return the list separator + SassSeparator separator() const override final { + return empty() ? SASS_UNDEF : SASS_COMMA; + } - Color_HSLA* copyAsHSLA() const override; - Color_HSLA* toHSLA() override { return this; } + // Return the length of this item as a list + size_t lengthAsList() const override { + return size(); + } - bool operator< (const Expression& rhs) const override; - bool operator== (const Expression& rhs) const override; + // Search the position of the given value + size_t indexOf(Value* value) override final; - ATTACH_AST_OPERATIONS(Color_HSLA) - ATTACH_CRTP_PERFORM_METHODS() - }; + // Return list with two items (key and value) + Value* getPairAsList(size_t idx); - ////////////////////////////// - // Errors from Sass_Values. - ////////////////////////////// - class Custom_Error final : public Value { - ADD_CONSTREF(sass::string, message) - public: - Custom_Error(SourceSpan pstate, sass::string msg); - bool operator< (const Expression& rhs) const override; - bool operator== (const Expression& rhs) const override; - ATTACH_AST_OPERATIONS(Custom_Error) - ATTACH_CRTP_PERFORM_METHODS() - }; + // Only used for nth sass function + // Doesn't allow overflow of index (throw error) + // Allows negative index but no overflow either + Value* getValueAt(Value* index, Logger& logger) override final; - ////////////////////////////// - // Warnings from Sass_Values. - ////////////////////////////// - class Custom_Warning final : public Value { - ADD_CONSTREF(sass::string, message) - public: - Custom_Warning(SourceSpan pstate, sass::string msg); - bool operator< (const Expression& rhs) const override; - bool operator== (const Expression& rhs) const override; - ATTACH_AST_OPERATIONS(Custom_Warning) - ATTACH_CRTP_PERFORM_METHODS() + // Implement interface for base Value class + size_t hash() const override final; + enum SassValueType getTag() const override final { return SASS_MAP; } + const sass::string& type() const override final { return Strings::map; } + + // Implement equality comparators for base value class + bool operator== (const Value& rhs) const override final; + // Implement same class compare operator + bool operator== (const Map& rhs) const; + + // Implement type fetcher for base value class (throws in base implementation) + Map* assertMap(Logger& logger, const sass::string& name) override { return this; } + + // Main entry point for Value Visitor pattern + void accept(ValueVisitor* visitor) override final { + return visitor->visitMap(this); + } + Value* accept(ValueVisitor* visitor) override final { + return visitor->visitMap(this); + } + // Copy operations for childless items + Map* copy(SASS_MEMORY_ARGS bool childless) const override final { + return SASS_MEMORY_NEW_DBG(Map, this); + } + + protected: + + // Clone all items in-place + Map* cloneChildren(SASS_MEMORY_ARGS_VOID) override final { + for (auto it = Hashed::begin(); it != Hashed::end(); ++it) { + it.value() = it.value()->copy(SASS_MEMORY_PARAMS_VOID); + it.value()->cloneChildren(SASS_MEMORY_PARAMS_VOID); + } + return this; + } + + IMPLEMENT_ISA_CASTER(Map); }; - //////////// - // Booleans. - //////////// - class Boolean final : public Value { - HASH_PROPERTY(bool, value) - mutable size_t hash_; + /////////////////////////////////////////////////////////////////////// + // Lists of values, both comma- and space-separated (distinguished by a + // type-tag.) Also used to represent variable-length argument lists. + /////////////////////////////////////////////////////////////////////// + + class List : public Value, public Vectorized + { + private: + + enum SassSeparator separator_; + ADD_CONSTREF(bool, hasBrackets); + public: - Boolean(SourceSpan pstate, bool val); - operator bool() override { return value_; } - sass::string type() const override { return "bool"; } - static sass::string type_name() { return "bool"; } + // Value constructor + List(const SourceSpan& pstate, + const ValueVector& values = {}, + enum SassSeparator separator = SASS_SPACE, + bool hasBrackets = false); - size_t hash() const override; + // Value constructor + List(const SourceSpan& pstate, + ValueVector&& values, + enum SassSeparator separator = SASS_SPACE, + bool hasBrackets = false); - bool is_false() override { return !value_; } + // Copy constructor + List(const List* ptr); - bool operator< (const Expression& rhs) const override; - bool operator== (const Expression& rhs) const override; + // Return the list separator + SassSeparator separator() const override final { + return separator_; + } + + // Set the list separator + void separator(SassSeparator separator) { + separator_ = separator; + } - ATTACH_AST_OPERATIONS(Boolean) - ATTACH_CRTP_PERFORM_METHODS() + // Return the length of this item as a list + size_t lengthAsList() const override final { + return size(); + } + + // Check if list has surrounding brackets + bool hasBrackets() override final { + return hasBrackets_; + } + + // Check if value would render empty + bool isBlank() const override final { + if (hasBrackets_) return false; + for (const Value* value : elements()) { + if (!value->isBlank()) return false; + } + return true; + } + + // Search the position of the given value + size_t indexOf(Value* value) override final; + + // Only used for nth sass function + // Allows negative index but no overflow either + // Doesn't allow overflow of index (throw error) + Value* getValueAt(Value* index, Logger& logger) override final; + + // Implement interface for base Value class + virtual size_t hash() const override; + enum SassValueType getTag() const override final { return SASS_LIST; } + virtual const sass::string& type() const override { return Strings::list; } + + // Implement equality comparators for base value class + virtual bool operator== (const Value& rhs) const override; + // Implement same class compare operator + bool operator== (const List& rhs) const; + + // Implement type fetcher for base value class (throws in base implementation) + Map* assertMap(Logger& logger, const sass::string& name) override final; + + // Main entry point for Value Visitor pattern + void accept(ValueVisitor* visitor) override final { + return visitor->visitList(this); + } + Value* accept(ValueVisitor* visitor) override final { + return visitor->visitList(this); + } + // Copy operations for childless items + List* copy(SASS_MEMORY_ARGS bool childless) const override { + return SASS_MEMORY_NEW_DBG(List, this); + } + + protected: + + // Clone all items in-place + List* cloneChildren(SASS_MEMORY_ARGS_VOID) override { + for (ValueObj& entry : elements_) { + entry = entry->copy(SASS_MEMORY_PARAMS_VOID); + entry->cloneChildren(SASS_MEMORY_PARAMS_VOID); + } + return this; + } + + IMPLEMENT_ISA_CASTER(List); }; - //////////////////////////////////////////////////////////////////////// - // Abstract base class for Sass string values. Includes interpolated and - // "flat" strings. - //////////////////////////////////////////////////////////////////////// - class String : public Value { + /////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////// + + class ArgumentList final : public List + { + private: + + ValueFlatMap _keywords; + mutable bool _wereKeywordsAccessed; + public: - String(SourceSpan pstate, bool delayed = false); - static sass::string type_name() { return "string"; } - virtual ~String() = 0; - virtual void rtrim() = 0; - virtual bool operator<(const Expression& rhs) const override { - return this->to_string() < rhs.to_string(); - }; - virtual bool operator==(const Expression& rhs) const override { - return this->to_string() == rhs.to_string(); - }; - ATTACH_VIRTUAL_AST_OPERATIONS(String); - ATTACH_CRTP_PERFORM_METHODS() + + // Value copy constructor + ArgumentList(const SourceSpan& pstate, + SassSeparator sep = SASS_SPACE, + ValueVector&& values = {}, + ValueFlatMap&& keywords = {}); + + // Value move constructor + ArgumentList(const SourceSpan& pstate, + SassSeparator sep = SASS_SPACE, + const ValueVector& values = {}, + const ValueFlatMap& keywords = {}); + + // Copy constructor + ArgumentList(const ArgumentList* ptr); + + ValueFlatMap& keywords() { + _wereKeywordsAccessed = true; + return _keywords; + } + + bool wereKeywordsAccessed() const { + return _wereKeywordsAccessed; + } + + bool hasAllKeywordsConsumed() const { + return _keywords.empty() || + _wereKeywordsAccessed; + } + + Map* keywordsAsSassMap() const; + + // Implement interface for base Value class + size_t hash() const override final; + const sass::string& type() const override final { return Strings::arglist; } + + ArgumentList* assertArgumentList(Logger& logger, const sass::string& name = Strings::empty) override final { + return this; + } + + + // Implement equality comparators for base value class + bool operator== (const Value& rhs) const override final; + // Implement same class compare operator + bool operator== (const ArgumentList& rhs) const; + + // Copy operations for childless items + ArgumentList* copy(SASS_MEMORY_ARGS bool childless) const override final { + return SASS_MEMORY_NEW_DBG(ArgumentList, this); + } + + protected: + + // Clone all items in-place + ArgumentList* cloneChildren(SASS_MEMORY_ARGS_VOID) override final { + for (auto it : _keywords) { + it.second = it.second->copy(SASS_MEMORY_PARAMS_VOID); + it.second->cloneChildren(SASS_MEMORY_PARAMS_VOID); + } + return this; + } + + IMPLEMENT_ISA_CASTER(ArgumentList); }; - inline String::~String() { }; /////////////////////////////////////////////////////////////////////// - // Interpolated strings. Meant to be reduced to flat strings during the - // evaluation phase. + // A sass function reference. /////////////////////////////////////////////////////////////////////// - class String_Schema final : public String, public Vectorized { - ADD_PROPERTY(bool, css) - mutable size_t hash_; + class Function final : public Value + { + private: + + ADD_CONSTREF(sass::string, cssName); + ADD_CONSTREF(CallableObj, callable); + public: - String_Schema(SourceSpan pstate, size_t size = 0, bool css = true); - sass::string type() const override { return "string"; } - static sass::string type_name() { return "string"; } + // Value constructor + Function( + const SourceSpan& pstate, + CallableObj callable); + + // Value constructor + Function( + const SourceSpan& pstate, + const sass::string& cssName); + + // Copy constructor + Function(const Function* ptr); + + // Implement interface for base Value class + size_t hash() const override final { return 0; } + enum SassValueType getTag() const override final { return SASS_FUNCTION; } + const sass::string& type() const override final { return Strings::function; } + + // Implement equality comparators for base value class + bool operator== (const Value& rhs) const override final; + // Implement same class compare operator + bool operator== (const Function& rhs) const; - bool is_left_interpolant(void) const override; - bool is_right_interpolant(void) const override; + Function* assertFunction(Logger& logger, const sass::string& name = Strings::empty) override final { return this; } - bool has_interpolants(); - void rtrim() override; - size_t hash() const override; - virtual void set_delayed(bool delayed) override; + // Main entry point for Value Visitor pattern + void accept(ValueVisitor* visitor) override final { + return visitor->visitFunction(this); + } + Value* accept(ValueVisitor* visitor) override final { + return visitor->visitFunction(this); + } + // Copy operations for childless items + Function* copy(SASS_MEMORY_ARGS bool childless) const override final { + return SASS_MEMORY_NEW_DBG(Function, this); + } - bool operator< (const Expression& rhs) const override; - bool operator==(const Expression& rhs) const override; - ATTACH_AST_OPERATIONS(String_Schema) - ATTACH_CRTP_PERFORM_METHODS() + IMPLEMENT_ISA_CASTER(Function); }; - //////////////////////////////////////////////////////// - // Flat strings -- the lowest level of raw textual data. - //////////////////////////////////////////////////////// - class String_Constant : public String { - ADD_PROPERTY(char, quote_mark) - HASH_CONSTREF(sass::string, value) - protected: - mutable size_t hash_; + /////////////////////////////////////////////////////////////////////// + // A calculation. + /////////////////////////////////////////////////////////////////////// + + class Calculation final : public Value, public CalcItem + { + private: + + ADD_CONSTREF(sass::string, name) + + ADD_CONSTREF(sass::vector, arguments) + public: - String_Constant(SourceSpan pstate, sass::string val, bool css = true); - String_Constant(SourceSpan pstate, const char* beg, bool css = true); - String_Constant(SourceSpan pstate, const char* beg, const char* end, bool css = true); - String_Constant(SourceSpan pstate, const Token& tok, bool css = true); - sass::string type() const override { return "string"; } - static sass::string type_name() { return "string"; } - bool is_invisible() const override; - virtual void rtrim() override; - size_t hash() const override; - bool operator< (const Expression& rhs) const override; - bool operator==(const Expression& rhs) const override; - // quotes are forced on inspection - virtual sass::string inspect() const override; - ATTACH_AST_OPERATIONS(String_Constant) - ATTACH_CRTP_PERFORM_METHODS() + + // Value constructor + Calculation(const SourceSpan& pstate, + const sass::string& name, + const sass::vector arguments); + + // Copy constructor + Calculation(const Calculation* ptr); + + // CalcOperation can't be simplified further + AstNode* simplify(Logger& logger) override final; + + // Implement simple checkers for base value class + bool isNull() const override final { return false; } + bool isBlank() const override final { return false; } + bool isTruthy() const override final { return true; } + + // Implement interface for base Value class + size_t hash() const override final; + + enum SassValueType getTag() const override final { return SASS_CALCULATION; } + const sass::string& type() const override final { return Strings::calculation; } + + // Implement equality comparators for base value class + bool operator== (const Value& rhs) const override final; + + Value* plus(Value* other, Logger& logger, const SourceSpan& pstate) const override final; + Value* minus(Value* other, Logger& logger, const SourceSpan& pstate) const override final; + Value* unaryPlus(Logger& logger, const SourceSpan& pstate) const override final; + Value* unaryMinus(Logger& logger, const SourceSpan& pstate) const override final; + + // Main entry point for Value Visitor pattern + void accept(ValueVisitor* visitor) override final { + return visitor->visitCalculation(this); + } + Value* accept(ValueVisitor* visitor) override final { + return visitor->visitCalculation(this); + } + + // Copy operations for childless items + Calculation* copy(SASS_MEMORY_ARGS bool childless) const override final { + return SASS_MEMORY_NEW_DBG(Calculation, this); + } + + + Calculation* assertCalculation(Logger& logger, const sass::string& name = Strings::empty) override final { + return this; + } + + IMPLEMENT_ISA_CASTER(Calculation); }; - //////////////////////////////////////////////////////// - // Possibly quoted string (unquote on instantiation) - //////////////////////////////////////////////////////// - class String_Quoted final : public String_Constant { + /////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////// + + + class Mixin final : public Value + { + public: - String_Quoted(SourceSpan pstate, sass::string val, char q = 0, - bool keep_utf8_escapes = false, bool skip_unquoting = false, - bool strict_unquoting = true, bool css = true); - bool operator< (const Expression& rhs) const override; - bool operator==(const Expression& rhs) const override; - // quotes are forced on inspection - sass::string inspect() const override; - ATTACH_AST_OPERATIONS(String_Quoted) - ATTACH_CRTP_PERFORM_METHODS() - }; - ////////////////// - // The null value. - ////////////////// - class Null final : public Value { + ADD_CONSTREF(CallableObj, callable); + public: - Null(SourceSpan pstate); - sass::string type() const override { return "null"; } - static sass::string type_name() { return "null"; } - bool is_invisible() const override { return true; } - operator bool() override { return false; } - bool is_false() override { return true; } - size_t hash() const override; + // Value constructor + Mixin( + const SourceSpan& pstate, + Callable* callable); - bool operator< (const Expression& rhs) const override; - bool operator== (const Expression& rhs) const override; + // Copy constructor + Mixin(const Mixin* ptr); + + // CalcOperation can't be simplified further + // AstNode* simplify(Logger& logger) override final { return this; } + + // Assert and return a mixin value or throws if incompatible + Mixin* assertMixin(Logger& logger, const sass::string& name = Strings::empty) override final { + return this; + } + + // Implement simple checkers for base value class + bool isNull() const override final { return false; } + bool isBlank() const override final { return false; } + bool isTruthy() const override final { return true; } + + // Implement interface for base Value class + size_t hash() const override final; + + enum SassValueType getTag() const override final { return SASS_MIXIN; } + const sass::string& type() const override final { return Strings::mixin; } + + // Implement equality comparators for base value class + bool operator== (const Value& rhs) const override final; + bool operator== (const Mixin& rhs) const; + + // Main entry point for Value Visitor pattern + void accept(ValueVisitor* visitor) override final { + return visitor->visitMixin(this); + } + Value* accept(ValueVisitor* visitor) override final { + return visitor->visitMixin(this); + } + + // Copy operations for childless items + Mixin* copy(SASS_MEMORY_ARGS bool childless) const override final { + return SASS_MEMORY_NEW_DBG(Mixin, this); + } + + IMPLEMENT_ISA_CASTER(Mixin); - ATTACH_AST_OPERATIONS(Null) - ATTACH_CRTP_PERFORM_METHODS() }; - ////////////////////////////////// - // The Parent Reference Expression. - ////////////////////////////////// - class Parent_Reference final : public Value { + + /////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////// + + class CalcOperation : public Value, public CalcItem + { + public: - Parent_Reference(SourceSpan pstate); - sass::string type() const override { return "parent"; } - static sass::string type_name() { return "parent"; } - bool operator< (const Expression& rhs) const override { - return false; // they are always equal - } - bool operator==(const Expression& rhs) const override { - return true; // they are always equal - }; - ATTACH_AST_OPERATIONS(Parent_Reference) - ATTACH_CRTP_PERFORM_METHODS() + + ADD_CONSTREF(SassOperator, op); + ADD_CONSTREF(AstNodeObj, left); + ADD_CONSTREF(AstNodeObj, right); + + public: + + // Value constructor + CalcOperation( + const SourceSpan& pstate, + const SassOperator op, + AstNode* left, + AstNode* right); + + // Copy constructor + CalcOperation(const CalcOperation* ptr); + + // CalcOperation can't be simplified further + AstNode* simplify(Logger& logger) override final { return this; } + + // Implement simple checkers for base value class + bool isNull() const override final { return false; } + bool isBlank() const override final { return false; } + bool isTruthy() const override final { return true; } + + // Implement interface for base Value class + size_t hash() const override final; + + enum SassValueType getTag() const override final { return SASS_CALC_OPERATION; } + const sass::string& type() const override final { return Strings::calcoperation; } + + // Implement equality comparators for base value class + bool operator== (const Value& rhs) const override final; + + // Main entry point for Value Visitor pattern + void accept(ValueVisitor* visitor) override final { + return visitor->visitCalcOperation(this); + } + Value* accept(ValueVisitor* visitor) override final { + return visitor->visitCalcOperation(this); + } + + // Copy operations for childless items + CalcOperation* copy(SASS_MEMORY_ARGS bool childless) const override final { + return SASS_MEMORY_NEW_DBG(CalcOperation, this); + } + + IMPLEMENT_ISA_CASTER(CalcOperation); + }; + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + } #endif diff --git a/src/b64/cencode.h b/src/b64/cencode.h deleted file mode 100644 index 1d71e83fdb..0000000000 --- a/src/b64/cencode.h +++ /dev/null @@ -1,32 +0,0 @@ -/* -cencode.h - c header for a base64 encoding algorithm - -This is part of the libb64 project, and has been placed in the public domain. -For details, see http://sourceforge.net/projects/libb64 -*/ - -#ifndef BASE64_CENCODE_H -#define BASE64_CENCODE_H - -typedef enum -{ - step_A, step_B, step_C -} base64_encodestep; - -typedef struct -{ - base64_encodestep step; - char result; - int stepcount; -} base64_encodestate; - -void base64_init_encodestate(base64_encodestate* state_in); - -char base64_encode_value(char value_in); - -int base64_encode_block(const char* plaintext_in, int length_in, char* code_out, base64_encodestate* state_in); - -int base64_encode_blockend(char* code_out, base64_encodestate* state_in); - -#endif /* BASE64_CENCODE_H */ - diff --git a/src/b64/cencode.hpp b/src/b64/cencode.hpp new file mode 100644 index 0000000000..8849d7393c --- /dev/null +++ b/src/b64/cencode.hpp @@ -0,0 +1,41 @@ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +/* cencode.h - c source to a base64 encoding algorithm implementation */ +/* Part of the libb64 project, and has been placed in the public domain. */ +/* For details, see http://sourceforge.net/projects/libb64 */ +/*****************************************************************************/ + +#ifndef BASE64_CENCODE_HPP +#define BASE64_CENCODE_HPP + +#ifdef _MSC_VER +#pragma warning(disable : 26812) +#endif + +namespace base64 { + + typedef enum + { + step_A, step_B, step_C + } base64_encodestep; + + typedef struct + { + base64_encodestep step; + char result; + int stepcount; + } base64_encodestate; + + void base64_init_encodestate(base64_encodestate* state_in); + + char base64_encode_value(char value_in); + + int base64_encode_block(const char* plaintext_in, int length_in, char* code_out, base64_encodestate* state_in); + + int base64_encode_blockend(char* code_out, base64_encodestate* state_in); + +} + +#endif /* BASE64_CENCODE_H */ + diff --git a/src/b64/encode.h b/src/b64/encode.hpp similarity index 59% rename from src/b64/encode.h rename to src/b64/encode.hpp index 92df8ec702..66cb1e66d8 100644 --- a/src/b64/encode.h +++ b/src/b64/encode.hpp @@ -1,28 +1,29 @@ -// :mode=c++: -/* -encode.h - c++ wrapper for a base64 encoding algorithm +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +/* encode.hpp - c source to a base64 encoding algorithm implementation */ +/* Part of the libb64 project, and has been placed in the public domain. */ +/* For details, see http://sourceforge.net/projects/libb64 */ +/*****************************************************************************/ -This is part of the libb64 project, and has been placed in the public domain. -For details, see http://sourceforge.net/projects/libb64 -*/ -#ifndef BASE64_ENCODE_H -#define BASE64_ENCODE_H +#ifndef BASE64_ENCODE_HPP +#define BASE64_ENCODE_HPP #include +#include "cencode.hpp" + +#ifndef BASE64_BUFFERSIZE +#define BASE64_BUFFERSIZE 255 +#endif namespace base64 { - extern "C" - { - #include "cencode.h" - } - struct encoder { base64_encodestate _state; int _buffersize; - encoder(int buffersize_in = BUFFERSIZE) + encoder(int buffersize_in = BASE64_BUFFERSIZE) : _buffersize(buffersize_in) { base64_init_encodestate(&_state); @@ -47,9 +48,9 @@ namespace base64 { base64_init_encodestate(&_state); // - const int N = _buffersize; + size_t N = _buffersize; char* plaintext = new char[N]; - char* code = new char[2*N]; + char* code = new char[N*2]; int plainlength; int codelength; diff --git a/src/backtrace.cpp b/src/backtrace.cpp deleted file mode 100644 index 9e6f6a5fea..0000000000 --- a/src/backtrace.cpp +++ /dev/null @@ -1,50 +0,0 @@ -#include "backtrace.hpp" - -namespace Sass { - - const sass::string traces_to_string(Backtraces traces, sass::string indent) { - - sass::ostream ss; - sass::string cwd(File::get_cwd()); - - bool first = true; - size_t i_beg = traces.size() - 1; - size_t i_end = sass::string::npos; - for (size_t i = i_beg; i != i_end; i --) { - - const Backtrace& trace = traces[i]; - - // make path relative to the current directory - sass::string rel_path(File::abs2rel(trace.pstate.getPath(), cwd, cwd)); - - // skip functions on error cases (unsure why ruby sass does this) - // if (trace.caller.substr(0, 6) == ", in f") continue; - - if (first) { - ss << indent; - ss << "on line "; - ss << trace.pstate.getLine(); - ss << ":"; - ss << trace.pstate.getColumn(); - ss << " of " << rel_path; - // ss << trace.caller; - first = false; - } else { - ss << trace.caller; - ss << std::endl; - ss << indent; - ss << "from line "; - ss << trace.pstate.getLine(); - ss << ":"; - ss << trace.pstate.getColumn(); - ss << " of " << rel_path; - } - - } - - ss << std::endl; - return ss.str(); - - } - -}; diff --git a/src/backtrace.hpp b/src/backtrace.hpp index 26efb985da..1cc3683c5f 100644 --- a/src/backtrace.hpp +++ b/src/backtrace.hpp @@ -1,28 +1,110 @@ #ifndef SASS_BACKTRACE_H #define SASS_BACKTRACE_H -#include -#include -#include "file.hpp" -#include "position.hpp" +#include "strings.hpp" +#include "source_span.hpp" +#include "ast_def_macros.hpp" + +// During runtime we need stack traces in order to produce meaningful +// error messages. Since the error catching might be done outside of +// the main compile function, certain values might already be garbage +// collected. Therefore we need to carry copies of those in any error. +// In order to optimize runtime, we don't want to create these copies +// during the evaluation stage, as most of the time we would throw them +// out right away. Therefore we only keep references during that phase +// (BackTrace), and copy them once an actual error is thrown (StackTrace). namespace Sass { - struct Backtrace { + class Traced { + public: + CAPI_WRAPPER(Traced, SassTrace); + virtual const SourceSpan& getPstate() const = 0; + virtual const sass::string& getName() const = 0; + virtual bool isFn() const = 0; + virtual ~Traced() {}; + }; + + // Holding actual copies + class StackTrace : public Traced { + + public: SourceSpan pstate; - sass::string caller; + sass::string name; + bool fn; + + StackTrace( + SourceSpan pstate, + sass::string name = Strings::empty, + bool fn = false) : + pstate(pstate), + name(name), + fn(fn) + {} - Backtrace(SourceSpan pstate, sass::string c = "") - : pstate(pstate), - caller(c) - { } + const SourceSpan& getPstate() const override final { + return pstate; + } + + const sass::string& getName() const override final { + return name; + } + + bool operator==(const StackTrace& other) const { + return pstate == other.pstate && + name == other.name && fn == other.fn; + } + + bool isFn() const override final { + return fn; + } }; - typedef sass::vector Backtraces; + // Holding only references + class BackTrace : public Traced { + + public: + + const SourceSpan& pstate; + const sass::string& name; + bool fn; + + BackTrace( + const SourceSpan& pstate, + const sass::string& name = Strings::empty, + bool fn = false) : + pstate(pstate), + name(name), + fn(fn) + {} + + const SourceSpan& getPstate() const override final { + return pstate; + } + + const sass::string& getName() const override final { + return name; + } + + bool isFn() const override final { + return fn; + } + + // Create copies on convert + operator StackTrace() + { + return StackTrace( + pstate, name, fn); + } + + }; - const sass::string traces_to_string(Backtraces traces, sass::string indent = "\t"); + // Some related and often used aliases + typedef sass::vector Traces; + typedef sass::vector BackTraces; + typedef sass::vector StackTraces; } diff --git a/src/base64vlq.cpp b/src/base64vlq.cpp deleted file mode 100644 index e8c3eea69b..0000000000 --- a/src/base64vlq.cpp +++ /dev/null @@ -1,47 +0,0 @@ -// sass.hpp must go before all system headers to get the -// __EXTENSIONS__ fix on Solaris. -#include "sass.hpp" - -#include "base64vlq.hpp" - -namespace Sass { - - sass::string Base64VLQ::encode(const int number) const - { - sass::string encoded = ""; - - int vlq = to_vlq_signed(number); - - do { - int digit = vlq & VLQ_BASE_MASK; - vlq >>= VLQ_BASE_SHIFT; - if (vlq > 0) { - digit |= VLQ_CONTINUATION_BIT; - } - encoded += base64_encode(digit); - } while (vlq > 0); - - return encoded; - } - - char Base64VLQ::base64_encode(const int number) const - { - int index = number; - if (index < 0) index = 0; - if (index > 63) index = 63; - return CHARACTERS[index]; - } - - int Base64VLQ::to_vlq_signed(const int number) const - { - return (number < 0) ? ((-number) << 1) + 1 : (number << 1) + 0; - } - - const char* Base64VLQ::CHARACTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; - - const int Base64VLQ::VLQ_BASE_SHIFT = 5; - const int Base64VLQ::VLQ_BASE = 1 << VLQ_BASE_SHIFT; - const int Base64VLQ::VLQ_BASE_MASK = VLQ_BASE - 1; - const int Base64VLQ::VLQ_CONTINUATION_BIT = VLQ_BASE; - -} diff --git a/src/base64vlq.hpp b/src/base64vlq.hpp index 2df8c6ee60..3d16a03b12 100644 --- a/src/base64vlq.hpp +++ b/src/base64vlq.hpp @@ -2,27 +2,52 @@ #define SASS_BASE64VLQ_H #include +#include "memory.hpp" namespace Sass { class Base64VLQ { + const char* CHARACTERS = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + + const int VLQ_BASE_SHIFT = 5; + const int VLQ_BASE = 1 << VLQ_BASE_SHIFT; + const int VLQ_BASE_MASK = VLQ_BASE - 1; + const int VLQ_CONTINUATION_BIT = VLQ_BASE; + public: - sass::string encode(const int number) const; + void encode(sass::string& buffer, const int number) const + { + int vlq = (number < 0) ? ((-number) << 1) + 1 : (number << 1) + 0; - private: + do { + int digit = vlq & VLQ_BASE_MASK; + vlq >>= VLQ_BASE_SHIFT; + if (vlq > 0) { + digit |= VLQ_CONTINUATION_BIT; + } + buffer += base64_encode(digit); + } while (vlq > 0); - char base64_encode(const int number) const; + } + + private: - int to_vlq_signed(const int number) const; + inline char base64_encode(const int number) const + { + int index = number; + if (index < 0) index = 0; + if (index > 63) index = 63; + return CHARACTERS[index]; + } - static const char* CHARACTERS; + // int to_vlq_signed(const int number) const + // { + // return (number < 0) ? ((-number) << 1) + 1 : (number << 1) + 0; + // } - static const int VLQ_BASE_SHIFT; - static const int VLQ_BASE; - static const int VLQ_BASE_MASK; - static const int VLQ_CONTINUATION_BIT; }; } diff --git a/src/bind.cpp b/src/bind.cpp deleted file mode 100644 index fdb375df06..0000000000 --- a/src/bind.cpp +++ /dev/null @@ -1,312 +0,0 @@ -#include "sass.hpp" -#include "bind.hpp" -#include "ast.hpp" -#include "backtrace.hpp" -#include "context.hpp" -#include "expand.hpp" -#include "eval.hpp" -#include -#include -#include - -namespace Sass { - - void bind(sass::string type, sass::string name, Parameters_Obj ps, Arguments_Obj as, Env* env, Eval* eval, Backtraces& traces) - { - sass::string callee(type + " " + name); - - std::map param_map; - List_Obj varargs = SASS_MEMORY_NEW(List, as->pstate()); - varargs->is_arglist(true); // enable keyword size handling - - for (size_t i = 0, L = as->length(); i < L; ++i) { - if (auto str = Cast((*as)[i]->value())) { - // force optional quotes (only if needed) - if (str->quote_mark()) { - str->quote_mark('*'); - } - } - } - - // Set up a map to ensure named arguments refer to actual parameters. Also - // eval each default value left-to-right, wrt env, populating env as we go. - for (size_t i = 0, L = ps->length(); i < L; ++i) { - Parameter_Obj p = ps->at(i); - param_map[p->name()] = p; - // if (p->default_value()) { - // env->local_frame()[p->name()] = p->default_value()->perform(eval->with(env)); - // } - } - - // plug in all args; if we have leftover params, deal with it later - size_t ip = 0, LP = ps->length(); - size_t ia = 0, LA = as->length(); - while (ia < LA) { - Argument_Obj a = as->at(ia); - if (ip >= LP) { - // skip empty rest arguments - if (a->is_rest_argument()) { - if (List_Obj l = Cast(a->value())) { - if (l->length() == 0) { - ++ ia; continue; - } - } - } - sass::ostream msg; - msg << "wrong number of arguments (" << LA << " for " << LP << ")"; - msg << " for `" << name << "'"; - return error(msg.str(), as->pstate(), traces); - } - Parameter_Obj p = ps->at(ip); - - // If the current parameter is the rest parameter, process and break the loop - if (p->is_rest_parameter()) { - // The next argument by coincidence provides a rest argument - if (a->is_rest_argument()) { - - // We should always get a list for rest arguments - if (List_Obj rest = Cast(a->value())) { - // create a new list object for wrapped items - List* arglist = SASS_MEMORY_NEW(List, - p->pstate(), - 0, - rest->separator(), - true); - // wrap each item from list as an argument - for (ExpressionObj item : rest->elements()) { - if (Argument_Obj arg = Cast(item)) { - arglist->append(SASS_MEMORY_COPY(arg)); // copy - } else { - arglist->append(SASS_MEMORY_NEW(Argument, - item->pstate(), - item, - "", - false, - false)); - } - } - // assign new arglist to environment - env->local_frame()[p->name()] = arglist; - } - // invalid state - else { - throw std::runtime_error("invalid state"); - } - } else if (a->is_keyword_argument()) { - - // expand keyword arguments into their parameters - List* arglist = SASS_MEMORY_NEW(List, p->pstate(), 0, SASS_COMMA, true); - env->local_frame()[p->name()] = arglist; - Map_Obj argmap = Cast(a->value()); - for (auto key : argmap->keys()) { - if (String_Constant_Obj str = Cast(key)) { - sass::string param = unquote(str->value()); - arglist->append(SASS_MEMORY_NEW(Argument, - key->pstate(), - argmap->at(key), - "$" + param, - false, - false)); - } else { - traces.push_back(Backtrace(key->pstate())); - throw Exception::InvalidVarKwdType(key->pstate(), traces, key->inspect(), a); - } - } - - } else { - - // create a new list object for wrapped items - List_Obj arglist = SASS_MEMORY_NEW(List, - p->pstate(), - 0, - SASS_COMMA, - true); - // consume the next args - while (ia < LA) { - // get and post inc - a = (*as)[ia++]; - // maybe we have another list as argument - List_Obj ls = Cast(a->value()); - // skip any list completely if empty - if (ls && ls->empty() && a->is_rest_argument()) continue; - - ExpressionObj value = a->value(); - if (Argument_Obj arg = Cast(value)) { - arglist->append(arg); - } - // check if we have rest argument - else if (a->is_rest_argument()) { - // preserve the list separator from rest args - if (List_Obj rest = Cast(a->value())) { - arglist->separator(rest->separator()); - - for (size_t i = 0, L = rest->length(); i < L; ++i) { - ExpressionObj obj = rest->value_at_index(i); - arglist->append(SASS_MEMORY_NEW(Argument, - obj->pstate(), - obj, - "", - false, - false)); - } - } - // no more arguments - break; - } - // wrap all other value types into Argument - else { - arglist->append(SASS_MEMORY_NEW(Argument, - a->pstate(), - a->value(), - a->name(), - false, - false)); - } - } - // assign new arglist to environment - env->local_frame()[p->name()] = arglist; - } - // consumed parameter - ++ip; - // no more parameters - break; - } - - // If the current argument is the rest argument, extract a value for processing - else if (a->is_rest_argument()) { - // normal param and rest arg - List_Obj arglist = Cast(a->value()); - if (!arglist) { - if (ExpressionObj arg = Cast(a->value())) { - arglist = SASS_MEMORY_NEW(List, a->pstate(), 1); - arglist->append(arg); - } - } - - // empty rest arg - treat all args as default values - if (!arglist || !arglist->length()) { - break; - } else { - if (arglist->length() > LP - ip && !ps->has_rest_parameter()) { - size_t arg_count = (arglist->length() + LA - 1); - sass::ostream msg; - msg << callee << " takes " << LP; - msg << (LP == 1 ? " argument" : " arguments"); - msg << " but " << arg_count; - msg << (arg_count == 1 ? " was passed" : " were passed."); - deprecated_bind(msg.str(), as->pstate()); - - while (arglist->length() > LP - ip) { - arglist->elements().erase(arglist->elements().end() - 1); - } - } - } - // otherwise move one of the rest args into the param, converting to argument if necessary - ExpressionObj obj = arglist->at(0); - if (!(a = Cast(obj))) { - Expression* a_to_convert = obj; - a = SASS_MEMORY_NEW(Argument, - a_to_convert->pstate(), - a_to_convert, - "", - false, - false); - } - arglist->elements().erase(arglist->elements().begin()); - if (!arglist->length() || (!arglist->is_arglist() && ip + 1 == LP)) { - ++ia; - } - - } else if (a->is_keyword_argument()) { - Map_Obj argmap = Cast(a->value()); - - for (auto key : argmap->keys()) { - String_Constant* val = Cast(key); - if (val == NULL) { - traces.push_back(Backtrace(key->pstate())); - throw Exception::InvalidVarKwdType(key->pstate(), traces, key->inspect(), a); - } - sass::string param = "$" + unquote(val->value()); - - if (!param_map.count(param)) { - sass::ostream msg; - msg << callee << " has no parameter named " << param; - error(msg.str(), a->pstate(), traces); - } - env->local_frame()[param] = argmap->at(key); - } - ++ia; - continue; - } else { - ++ia; - } - - if (a->name().empty()) { - if (env->has_local(p->name())) { - sass::ostream msg; - msg << "parameter " << p->name() - << " provided more than once in call to " << callee; - error(msg.str(), a->pstate(), traces); - } - // ordinal arg -- bind it to the next param - env->local_frame()[p->name()] = a->value(); - ++ip; - } - else { - // named arg -- bind it to the appropriately named param - if (!param_map.count(a->name())) { - if (ps->has_rest_parameter()) { - varargs->append(a); - } else { - sass::ostream msg; - msg << callee << " has no parameter named " << a->name(); - error(msg.str(), a->pstate(), traces); - } - } - if (param_map[a->name()]) { - if (param_map[a->name()]->is_rest_parameter()) { - sass::ostream msg; - msg << "argument " << a->name() << " of " << callee - << "cannot be used as named argument"; - error(msg.str(), a->pstate(), traces); - } - } - if (env->has_local(a->name())) { - sass::ostream msg; - msg << "parameter " << p->name() - << "provided more than once in call to " << callee; - error(msg.str(), a->pstate(), traces); - } - env->local_frame()[a->name()] = a->value(); - } - } - // EO while ia - - // If we make it here, we're out of args but may have leftover params. - // That's only okay if they have default values, or were already bound by - // named arguments, or if it's a single rest-param. - for (size_t i = ip; i < LP; ++i) { - Parameter_Obj leftover = ps->at(i); - // cerr << "env for default params:" << endl; - // env->print(); - // cerr << "********" << endl; - if (!env->has_local(leftover->name())) { - if (leftover->is_rest_parameter()) { - env->local_frame()[leftover->name()] = varargs; - } - else if (leftover->default_value()) { - Expression* dv = leftover->default_value()->perform(eval); - env->local_frame()[leftover->name()] = dv; - } - else { - // param is unbound and has no default value -- error - throw Exception::MissingArgument(as->pstate(), traces, name, leftover->name(), type); - } - } - } - - return; - } - - -} diff --git a/src/bind.hpp b/src/bind.hpp deleted file mode 100644 index cb56fae08d..0000000000 --- a/src/bind.hpp +++ /dev/null @@ -1,15 +0,0 @@ -#ifndef SASS_BIND_H -#define SASS_BIND_H - -#include -#include "backtrace.hpp" -#include "environment.hpp" -#include "ast_fwd_decl.hpp" - -namespace Sass { - - void bind(sass::string type, sass::string name, Parameters_Obj, Arguments_Obj, Env*, Eval*, Backtraces& traces); - -} - -#endif diff --git a/src/c2ast.cpp b/src/c2ast.cpp deleted file mode 100644 index 7a4880c968..0000000000 --- a/src/c2ast.cpp +++ /dev/null @@ -1,64 +0,0 @@ -#include "ast.hpp" -#include "units.hpp" -#include "position.hpp" -#include "backtrace.hpp" -#include "sass/values.h" -#include "ast_fwd_decl.hpp" -#include "error_handling.hpp" - -namespace Sass { - - Value* c2ast(union Sass_Value* v, Backtraces traces, SourceSpan pstate) - { - using std::strlen; - using std::strcpy; - Value* e = NULL; - switch (sass_value_get_tag(v)) { - case SASS_BOOLEAN: { - e = SASS_MEMORY_NEW(Boolean, pstate, !!sass_boolean_get_value(v)); - } break; - case SASS_NUMBER: { - e = SASS_MEMORY_NEW(Number, pstate, sass_number_get_value(v), sass_number_get_unit(v)); - } break; - case SASS_COLOR: { - e = SASS_MEMORY_NEW(Color_RGBA, pstate, sass_color_get_r(v), sass_color_get_g(v), sass_color_get_b(v), sass_color_get_a(v)); - } break; - case SASS_STRING: { - if (sass_string_is_quoted(v)) - e = SASS_MEMORY_NEW(String_Quoted, pstate, sass_string_get_value(v)); - else { - e = SASS_MEMORY_NEW(String_Constant, pstate, sass_string_get_value(v)); - } - } break; - case SASS_LIST: { - List* l = SASS_MEMORY_NEW(List, pstate, sass_list_get_length(v), sass_list_get_separator(v)); - for (size_t i = 0, L = sass_list_get_length(v); i < L; ++i) { - l->append(c2ast(sass_list_get_value(v, i), traces, pstate)); - } - l->is_bracketed(sass_list_get_is_bracketed(v)); - e = l; - } break; - case SASS_MAP: { - Map* m = SASS_MEMORY_NEW(Map, pstate); - for (size_t i = 0, L = sass_map_get_length(v); i < L; ++i) { - *m << std::make_pair( - c2ast(sass_map_get_key(v, i), traces, pstate), - c2ast(sass_map_get_value(v, i), traces, pstate)); - } - e = m; - } break; - case SASS_NULL: { - e = SASS_MEMORY_NEW(Null, pstate); - } break; - case SASS_ERROR: { - error("Error in C function: " + sass::string(sass_error_get_message(v)), pstate, traces); - } break; - case SASS_WARNING: { - error("Warning in C function: " + sass::string(sass_warning_get_message(v)), pstate, traces); - } break; - default: break; - } - return e; - } - -} diff --git a/src/c2ast.hpp b/src/c2ast.hpp deleted file mode 100644 index 006c490e71..0000000000 --- a/src/c2ast.hpp +++ /dev/null @@ -1,14 +0,0 @@ -#ifndef SASS_C2AST_H -#define SASS_C2AST_H - -#include "position.hpp" -#include "backtrace.hpp" -#include "ast_fwd_decl.hpp" - -namespace Sass { - - Value* c2ast(union Sass_Value* v, Backtraces traces, SourceSpan pstate); - -} - -#endif diff --git a/src/calculation.cpp b/src/calculation.cpp new file mode 100644 index 0000000000..9bb2c5d64b --- /dev/null +++ b/src/calculation.cpp @@ -0,0 +1,1262 @@ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#include "calculation.hpp" + +#include "eval.hpp" + +namespace Sass { + + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + /// Verifies that all the numbers in [args] aren't known to be incompatible + /// with one another, and that they don't have units that are too complex for + /// calculations. + static void _verifyCompatibleNumbers3(Logger& logger, const SourceSpan& pstate, const ValueVector& args, bool strict = true) { + // Note: this logic is largely duplicated in + // _EvaluateVisitor._verifyCompatibleNumbers and most changes here should + // also be reflected there. + for (auto arg : args) { + if (Number* nr = dynamic_cast(arg.ptr())) { + if (nr->isValidCssUnit() == false) { + throw Exception::IncompatibleCalcValue( + logger, *arg, nr->pstate()); + } + } + } + + for (unsigned int i = 0; i < args.size() - 1; i++) { + if (Number* nr1 = dynamic_cast(args[i].ptr())) { + for (unsigned int j = i + 1; j < args.size(); j++) { + if (Number* nr2 = dynamic_cast(args[j].ptr())) { + // if (number1.hasPossiblyCompatibleUnits(number2)) continue; + if (nr1->hasPossiblyCompatibleUnits(nr2, strict)) continue; + throw Exception::UnitMismatch(logger, nr1, nr2); + } + } + } + } + } + + static void _verifyCompatibleNumbers2(Logger& logger, const SourceSpan& pstate, sass::vector args, bool strict = true) { + // Note: this logic is largely duplicated in + // _EvaluateVisitor._verifyCompatibleNumbers and most changes here should + // also be reflected there. + for (auto arg : args) { + if (Number* nr = dynamic_cast(arg)) { + if (nr->isValidCssUnit() == false) { + throw Exception::IncompatibleCalcValue( + logger, *arg, nr->pstate()); + } + } + } + + for (unsigned int i = 0; i < args.size() - 1; i++) { + if (Number* nr1 = dynamic_cast(args[i])) { + for (unsigned int j = i + 1; j < args.size(); j++) { + if (Number* nr2 = dynamic_cast(args[j])) { + // if (number1.hasPossiblyCompatibleUnits(number2)) continue; + if (nr1->hasPossiblyCompatibleUnits(nr2, strict)) continue; + throw Exception::UnitMismatch(logger, nr1, nr2); + } + } + } + } + } + + static void _verifyLength(Logger& logger, const ValueVector& args, size_t len) + { + if (args.size() == len) return; + for (const auto& arg : args) + if (arg->isaString()) return; + if (args.size() > len) throw Exception::TooManyArguments(logger, args.size(), len); + else if (args.size() < len) throw Exception::TooFewArguments(logger, args.size(), len); + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + const double NaN = std::numeric_limits::quiet_NaN(); + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + Value* Calculation32::calc_sqrt(Logger& logger, const SourceSpan& pstate, const ValueVector& args) + { + if (args.size() > 1) throw Exception::TooManyArguments(logger, args.size(), 1); + else if (args.size() < 1) throw Exception::MissingArgument(logger, str_number); + AstNodeObj simplified = args[0]->simplify(logger); + auto* number = dynamic_cast(simplified.ptr()); + if (number == nullptr) return SASS_MEMORY_NEW( + Calculation, pstate, str_sqrt, { simplified }); + number->assertUnitless(logger, str_number); + auto result = std::sqrt(number->value()); + return SASS_MEMORY_NEW(Number, number->pstate(), result); + } + + Value* Calculation32::calc_sign2(Logger& logger, const SourceSpan& pstate, const ValueVector& args) + { + if (args.size() > 1) throw Exception::TooManyArguments(logger, args.size(), 1); + else if (args.size() < 1) throw Exception::MissingArgument(logger, str_number); + AstNodeObj simplified = args[0]->simplify(logger); + auto* number = dynamic_cast(simplified.ptr()); + if (number == nullptr) return SASS_MEMORY_NEW( + Calculation, pstate, str_sign, { simplified }); + double result = 0; + if (number->value() == 0.0) result = number->value(); + else if (std::isnan(number->value())) result = NaN; + else result = std::signbit(number->value()) ? -1 : 1; + return SASS_MEMORY_NEW(Number, number->pstate(), result, number); + } + + Value* Calculation32::calc_exp2(Logger& logger, const SourceSpan& pstate, const ValueVector& args) + { + if (args.size() > 1) throw Exception::TooManyArguments(logger, args.size(), 1); + else if (args.size() < 1) throw Exception::MissingArgument(logger, str_number); + AstNodeObj simplified = args[0]->simplify(logger); + auto* number = dynamic_cast(simplified.ptr()); + if (number == nullptr) return SASS_MEMORY_NEW( + Calculation, pstate, str_exp, { simplified }); + number->assertUnitless(logger, str_number); + double result = 0; + if (std::isnan(number->value())) result = NaN; + else result = std::exp(number->value()); + return SASS_MEMORY_NEW(Number, number->pstate(), result); + } + + Value* Calculation32::calc_abs(Logger& logger, const SourceSpan& pstate, const ValueVector& args) + { + if (args.size() > 1) throw Exception::TooManyArguments(logger, args.size(), 1); + else if (args.size() < 1) throw Exception::MissingArgument(logger, str_number); + AstNodeObj simplified = args[0]->simplify(logger); + if (const auto str = dynamic_cast(simplified.ptr())) { + if (str->isVar()) return SASS_MEMORY_NEW( + Calculation, pstate, str_abs, { simplified }); + } + auto* number = dynamic_cast(simplified.ptr()); + if (number == nullptr) return SASS_MEMORY_NEW( + Calculation, pstate, str_abs, { simplified }); + auto result = std::abs(number->value()); + return SASS_MEMORY_NEW(Number, number->pstate(), result, number); + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + Value* Calculation32::calc_sin(Logger& logger, const SourceSpan& pstate, const ValueVector& args) + { + if (args.size() > 1) throw Exception::TooManyArguments(logger, args.size(), 1); + else if (args.size() < 1) throw Exception::MissingArgument(logger, str_angle); + AstNodeObj simplified = args[0]->simplify(logger); + auto* number = dynamic_cast(simplified.ptr()); + if (number == nullptr) return SASS_MEMORY_NEW( + Calculation, pstate, str_sin, { simplified }); + double factor = number->factorToUnits(unit_rad); + if (factor == 0.0) throw Exception::NoAngleArgument(logger, number, str_angle); + auto result = std::sin(number->value() * factor); + return SASS_MEMORY_NEW(Number, number->pstate(), result); + } + + Value* Calculation32::calc_cos(Logger& logger, const SourceSpan& pstate, const ValueVector& args) + { + if (args.size() > 1) throw Exception::TooManyArguments(logger, args.size(), 1); + else if (args.size() < 1) throw Exception::MissingArgument(logger, str_angle); + AstNodeObj simplified = args[0]->simplify(logger); + auto* number = dynamic_cast(simplified.ptr()); + if (number == nullptr) return SASS_MEMORY_NEW( + Calculation, pstate, str_cos, { simplified }); + double factor = number->factorToUnits(unit_rad); + if (factor == 0.0) throw Exception::NoAngleArgument(logger, number, str_angle); + auto result = std::cos(number->value() * factor); + return SASS_MEMORY_NEW(Number, number->pstate(), result); + } + + Value* Calculation32::calc_tan(Logger& logger, const SourceSpan& pstate, const ValueVector& args) + { + if (args.size() > 1) throw Exception::TooManyArguments(logger, args.size(), 1); + else if (args.size() < 1) throw Exception::MissingArgument(logger, str_angle); + AstNodeObj simplified = args[0]->simplify(logger); + auto* number = dynamic_cast(simplified.ptr()); + if (number == nullptr) return SASS_MEMORY_NEW( + Calculation, pstate, str_tan, { simplified }); + double factor = number->factorToUnits(unit_rad); + if (factor == 0.0) throw Exception::NoAngleArgument(logger, number, str_angle); + auto result = std::tan(number->value() * factor); + return SASS_MEMORY_NEW(Number, number->pstate(), result); + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + Value* Calculation32::calc_asin(Logger& logger, const SourceSpan& pstate, const ValueVector& args) + { + if (args.size() > 1) throw Exception::TooManyArguments(logger, args.size(), 1); + else if (args.size() < 1) throw Exception::MissingArgument(logger, str_number); + AstNodeObj simplified = args[0]->simplify(logger); + auto* number = dynamic_cast(simplified.ptr()); + if (number == nullptr) return SASS_MEMORY_NEW( + Calculation, pstate, str_asin, { simplified }); + number->assertNoUnits(logger, str_number); + auto degs = std::asin(number->value()) * Constants::Math::RAD_TO_DEG; + return SASS_MEMORY_NEW(Number, number->pstate(), degs, unit_deg); + } + + Value* Calculation32::calc_acos(Logger& logger, const SourceSpan& pstate, const ValueVector& args) + { + if (args.size() > 1) throw Exception::TooManyArguments(logger, args.size(), 1); + else if (args.size() < 1) throw Exception::MissingArgument(logger, str_number); + AstNodeObj simplified = args[0]->simplify(logger); + auto* number = dynamic_cast(simplified.ptr()); + if (number == nullptr) return SASS_MEMORY_NEW( + Calculation, pstate, str_acos, { simplified }); + number->assertNoUnits(logger, str_number); + auto degs = std::acos(number->value()) * Constants::Math::RAD_TO_DEG; + return SASS_MEMORY_NEW(Number, number->pstate(), degs, unit_deg); + } + + Value* Calculation32::calc_atan(Logger& logger, const SourceSpan& pstate, const ValueVector& args) + { + if (args.size() > 1) throw Exception::TooManyArguments(logger, args.size(), 1); + else if (args.size() < 1) throw Exception::MissingArgument(logger, str_number); + AstNodeObj simplified = args[0]->simplify(logger); + auto* number = dynamic_cast(simplified.ptr()); + if (number == nullptr) return SASS_MEMORY_NEW( + Calculation, pstate, str_atan, { simplified }); + number->assertNoUnits(logger, str_number); + auto degs = std::atan(number->value()) * Constants::Math::RAD_TO_DEG; + return SASS_MEMORY_NEW(Number, number->pstate(), degs, unit_deg); + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + Value* Calculation32::calc_pow2(Logger& logger, const SourceSpan& pstate, const ValueVector& args) + { + _verifyLength(logger, args, 2); // Allows string to pass always + // else if (args.size() < 2) throw Exception::MissingArgument(logger, str_number); + AstNodeObj arg_dividend = args[0]->simplify(logger); + AstNodeObj arg_modulus = args.size() > 1 ? args[1]->simplify(logger) : nullptr; + // _verifyLength(args, 2); _verifyCompatibleNumbers(args); + // _verifyCompatibleNumbers2(logger, pstate, { arg_dividend, arg_modulus }); + if (NumberObj nr_base = dynamic_cast(arg_dividend.ptr())) { + if (NumberObj nr_exp = dynamic_cast(arg_modulus.ptr())) { + nr_base->assertNoUnits(logger, str_base); + nr_exp->assertNoUnits(logger, str_exp); + return SASS_MEMORY_NEW(Number, pstate, + std::pow(nr_base->value(), nr_exp->value())); + } + } + return SASS_MEMORY_NEW(Calculation, pstate, + str_pow, { arg_dividend, arg_modulus }); + } + + Value* Calculation32::calc_atan3(Logger& logger, const SourceSpan& pstate, const ValueVector& args) + { + if (args.size() > 2) throw Exception::TooManyArguments(logger, args.size(), 2); + else if (args.size() < 2) throw Exception::TooFewArguments(logger, args.size(), 2); + // else if (args.size() < 2) throw Exception::MissingArgument(logger, str_number); + AstNodeObj arg_dividend = args[0]->simplify(logger); + AstNodeObj arg_modulus = args.size() > 1 ? args[1]->simplify(logger) : nullptr; + // _verifyLength(args, 2); _verifyCompatibleNumbers(args); + // _verifyCompatibleNumbers2(logger, pstate, { arg_dividend, arg_modulus }); + _verifyCompatibleNumbers2(logger, pstate, { arg_dividend, arg_modulus }, true); + if (NumberObj nr_base = dynamic_cast(arg_dividend.ptr())) { + if (NumberObj nr_exp = dynamic_cast(arg_modulus.ptr())) { + if (!((Units)*nr_base == unit_percent) || !((Units)*nr_exp == unit_percent)) { + double factor = nr_exp->getUnitConversionFactor(nr_base, true); + if (factor != 0 && nr_base->hasCompatibleUnits(nr_exp)) { + auto rads = std::atan2(nr_base->value(), nr_exp->value() * factor); + return SASS_MEMORY_NEW(Number, pstate, rads * Constants::Math::RAD_TO_DEG, unit_deg); + } + } + } + } + return SASS_MEMORY_NEW(Calculation, pstate, + str_atan2, { arg_dividend, arg_modulus }); + } + + + Value* Calculation32::calc_mod2(Logger& logger, const SourceSpan& pstate, const ValueVector& args) + { + if (args.size() > 2) throw Exception::TooManyArguments(logger, args.size(), 2); + else if (args.size() < 2) throw Exception::TooFewArguments(logger, args.size(), 2); + // else if (args.size() < 2) throw Exception::MissingArgument(logger, str_number); + AstNodeObj arg_dividend = args[0]->simplify(logger); + AstNodeObj arg_modulus = args.size() > 1 ? args[1]->simplify(logger) : nullptr; + // _verifyLength(args, 2); _verifyCompatibleNumbers(args); + _verifyCompatibleNumbers2(logger, pstate, { arg_dividend, arg_modulus }); + if (NumberObj dividend_nr = dynamic_cast(arg_dividend.ptr())) { + if (NumberObj modulus_nr = dynamic_cast(arg_modulus.ptr())) { + double factor = dividend_nr->getUnitConversionFactor(modulus_nr, true); + if (factor == 0.0) { + if (dividend_nr->isCustomUnit() || modulus_nr->isCustomUnit()) { + return SASS_MEMORY_NEW(Calculation, pstate, + str_mod, { arg_dividend, arg_modulus }); + } + throw Exception::UnitMismatch(logger, dividend_nr, modulus_nr); + } + return dividend_nr->modulo(modulus_nr, logger, pstate); + } + } + return SASS_MEMORY_NEW(Calculation, pstate, + str_mod, { arg_dividend, arg_modulus }); + } + + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + /// Creates a `rem()` calculation with the given [dividend] and [modulus]. + /// + /// Each argument must be either a [SassNumber], a [SassCalculation], an + /// unquoted [SassString], or a [CalculationOperation]. + /// + /// This automatically simplifies the calculation, so it may return a + /// [SassNumber] rather than a [SassCalculation]. It throws an exception if it + /// can determine that the calculation will definitely produce invalid CSS. + /// + /// This may be passed fewer than two arguments, but only if one of the + /// arguments is an unquoted `var()` string. + Value* Calculation32::calc_rem2(Logger& logger, const SourceSpan& pstate, const ValueVector& args) + { + if (args.size() > 2) throw Exception::TooManyArguments(logger, args.size(), 2); + else if (args.size() < 2) throw Exception::TooFewArguments(logger, args.size(), 2); + AstNodeObj arg_dividend = args[0]->simplify(logger); + AstNodeObj arg_modulus = args.size() > 1 ? args[1]->simplify(logger) : nullptr; + // _verifyLength(args, 2); _verifyCompatibleNumbers(args); + _verifyCompatibleNumbers2(logger, pstate, { arg_dividend, arg_modulus }); + if (NumberObj dividend_nr = dynamic_cast(arg_dividend.ptr())) { + if (NumberObj modulus_nr = dynamic_cast(arg_modulus.ptr())) { + double factor = dividend_nr->getUnitConversionFactor(modulus_nr, true); + if (factor == 0.0) { + if (dividend_nr->isCustomUnit() || modulus_nr->isCustomUnit()) { + return SASS_MEMORY_NEW(Calculation, pstate, + str_rem, { arg_dividend, arg_modulus }); + } + throw Exception::UnitMismatch(logger, dividend_nr, modulus_nr); + } + NumberObj result = Cast(dividend_nr->modulo(modulus_nr, logger, pstate)); + double div = dividend_nr->value(), mod = modulus_nr->value(); + if (std::signbit(div) == std::signbit(mod)) return result.detach(); + if (std::isinf(mod)) return dividend_nr.detach(); + if (result->value() == 0.0) return result->unaryMinus(logger, pstate); + return result->minus(modulus_nr, logger, pstate); + } + } + return SASS_MEMORY_NEW(Calculation, pstate, + str_rem, { arg_dividend, arg_modulus }); + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + /// Creates a `clamp()` calculation with the given [min], [value], and [max]. + /// + /// Each argument must be either a [SassNumber], a [SassCalculation], an + /// unquoted [SassString], or a [CalculationOperation]. + /// + /// This automatically simplifies the calculation, so it may return a + /// [SassNumber] rather than a [SassCalculation]. It throws an exception if it + /// can determine that the calculation will definitely produce invalid CSS. + /// + /// This may be passed fewer than three arguments, but only if one of the + /// arguments is an unquoted `var()` string. + Value* Calculation32::calc_clamp(Logger& logger, const SourceSpan& pstate, const ValueVector& args) + { + if (args.size() < 1) throw Exception::MissingArgument(logger, str_number); + AstNodeObj arg_min = args[0]->simplify(logger); + AstNodeObj arg_val = args.size() > 1 ? args[1]->simplify(logger) : nullptr; + AstNodeObj arg_max = args.size() > 2 ? args[2]->simplify(logger) : nullptr; + NumberObj nr_min = Cast(arg_min); + NumberObj nr_val = Cast(arg_val); + NumberObj nr_max = Cast(arg_max); + + if (nr_min && nr_val && nr_max) { + if (nr_min->hasCompatibleUnits(nr_val) && nr_max->hasCompatibleUnits(nr_val)) { + if (nr_val->lessThanOrEquals(nr_min, logger, pstate)) return nr_min.detach(); + if (nr_val->greaterThanOrEquals(nr_max, logger, pstate)) return nr_max.detach(); + else return nr_val.detach(); + } + } + _verifyCompatibleNumbers2(logger, pstate, { nr_min, nr_val }); + _verifyCompatibleNumbers2(logger, pstate, { nr_min, nr_max }); + _verifyLength(logger, args, 3); // Allows string to pass always + return SASS_MEMORY_NEW(Calculation, pstate, + str_clamp, { arg_min, arg_val, arg_max }); + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + /// Creates a `min()` calculation with the given [arguments]. + /// + /// Each argument must be either a [SassNumber], a [SassCalculation], an + /// unquoted [SassString], or a [CalculationOperation]. It must be passed at + /// least one argument. + /// + /// This automatically simplifies the calculation, so it may return a + /// [SassNumber] rather than a [SassCalculation]. It throws an exception if it + /// can determine that the calculation will definitely produce invalid CSS. + Value* Calculation32::calc_min(Logger& logger, const SourceSpan& pstate, const ValueVector& args) + { + if (args.empty()) throw Exception::MustHaveArguments(logger, str_min); + sass::vector simplified(args.size()); + std::transform(args.begin(), args.end(), + simplified.begin(), [&](ValueObj value) { + return value->simplify(logger); + }); + // find min number now + NumberObj min = nullptr; + for (size_t i = 0; i < args.size(); i++) { + Value* val = Cast(simplified[i]); + if (val->isaCalculation() || val->isaCalcOperation()) { + _verifyCompatibleNumbers3(logger, pstate, args); + return SASS_MEMORY_NEW(Calculation, + pstate, str_min, std::move(simplified)); + } + if (auto str = val->isaString()) { + //_verifyCompatibleNumbers3(logger, pstate, args); + if (str->isVar()) { + _verifyCompatibleNumbers3(logger, pstate, args); + return SASS_MEMORY_NEW(Calculation, + pstate, str_min, std::move(simplified)); + } + } + Number* nr = val->assertNumber(logger, str_empty); + if (nr == nullptr) { + _verifyCompatibleNumbers3(logger, pstate, args); + return SASS_MEMORY_NEW(Calculation, + pstate, str_min, std::move(simplified)); + } + if (min == nullptr) { min = nr; continue; } + // _verifyCompatibleNumbers2(logger, pstate, { min, nr }, false); + double factor = nr->getUnitConversionFactor(min, false); + if (factor == 0.0) { + // throw Exception::UnitMismatch(logger, min, nr); + _verifyCompatibleNumbers3(logger, pstate, args); + return SASS_MEMORY_NEW(Calculation, + pstate, str_min, std::move(simplified)); + } + if (min->value() > nr->value() * factor) min = nr; + } + + // Return min number + return min.detach(); + } + + /// Creates a `max()` calculation with the given [arguments]. + /// + /// Each argument must be either a [SassNumber], a [SassCalculation], an + /// unquoted [SassString], or a [CalculationOperation]. It must be passed at + /// least one argument. + /// + /// This automatically simplifies the calculation, so it may return a + /// [SassNumber] rather than a [SassCalculation]. It throws an exception if it + /// can determine that the calculation will definitely produce invalid CSS. + Value* Calculation32::calc_max(Logger& logger, const SourceSpan& pstate, const ValueVector& args) + { + // std::cerr << "==== Execute max\n"; + if (args.empty()) throw Exception::MustHaveArguments(logger, str_max); + sass::vector simplified(args.size()); + std::transform(args.begin(), args.end(), + simplified.begin(), [&](ValueObj value) { + return value->simplify(logger); + }); + // find max number now + NumberObj max = nullptr; + for (size_t i = 0; i < simplified.size(); i++) { + Value* val = Cast(simplified[i]); + if (val->isaCalculation() || val->isaCalcOperation()) { + _verifyCompatibleNumbers3(logger, pstate, args); + return SASS_MEMORY_NEW(Calculation, + pstate, str_max, std::move(simplified)); + } + if (auto str = val->isaString()) { + if (str->isVar()) { + _verifyCompatibleNumbers3(logger, pstate, args); + return SASS_MEMORY_NEW(Calculation, + pstate, str_max, std::move(simplified)); + } + } + Number* nr = val->assertNumber(logger, str_empty); + if (nr == nullptr) { + _verifyCompatibleNumbers3(logger, pstate, args); + return SASS_MEMORY_NEW(Calculation, + pstate, str_max, std::move(simplified)); + } + if (max == nullptr) { max = nr; continue; } + //_verifyCompatibleNumbers2(logger, pstate, { max, nr }, false); + double factor = nr->getUnitConversionFactor(max, false); + if (factor == 0.0) { + _verifyCompatibleNumbers3(logger, pstate, args); + return SASS_MEMORY_NEW(Calculation, + pstate, str_max, std::move(simplified)); + } + if (max->value() < nr->value() * factor) max = nr; + } + // Return max number + return max.detach(); + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + /// Creates a `hypot()` calculation with the given [arguments]. + /// + /// Each argument must be either a [SassNumber], a [SassCalculation], an + /// unquoted [SassString], or a [CalculationOperation]. It must be passed at + /// least one argument. + /// + /// This automatically simplifies the calculation, so it may return a + /// [SassNumber] rather than a [SassCalculation]. It throws an exception if it + /// can determine that the calculation will definitely produce invalid CSS. + Value* Calculation32::calc_hypot(Logger& logger, const SourceSpan& pstate, const ValueVector& args) + { + if (args.empty()) throw Exception::MustHaveArguments(logger, str_max); + sass::vector simplified(args.size()); + std::transform(args.begin(), args.end(), + simplified.begin(), [&](ValueObj value) { + return value->simplify(logger); + }); + _verifyCompatibleNumbers3(logger, pstate, args); + Number* first = Cast(simplified[0]); + if (first == nullptr) { + return SASS_MEMORY_NEW(Calculation, pstate, + str_hypot, std::move(simplified)); + } + else if (first->hasUnit("%")) { + return SASS_MEMORY_NEW(Calculation, pstate, + str_hypot, std::move(simplified)); + } + else { + double subtotal = first->value() * first->value(); + for (size_t i = 1; i < simplified.size(); i++) { + Number* next = Cast(args[i]); + if (next == nullptr) return SASS_MEMORY_NEW(Calculation, + pstate, str_hypot, std::move(simplified)); + _verifyCompatibleNumbers2(logger, pstate, { first, next }); + double factor = next->getUnitConversionFactor(first); + if (factor == 0.0) { + if (first->isCustomUnit() || next->isCustomUnit()) { + return SASS_MEMORY_NEW(Calculation, pstate, + str_hypot, std::move(simplified)); + } + throw Exception::UnitMismatch(logger, first, next); + } + double value = next->value() * factor; // convert + subtotal += value * value; // square it + } + // Return the result in units of the first number + return SASS_MEMORY_NEW(Number, pstate, + std::sqrt(subtotal), first); + } + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + + // Returns [value] coerced to [number]'s units. + static Number* _matchUnits(double value, Number* number) + { + return SASS_MEMORY_NEW(Number, number->pstate(), value, number); + } + + + Number* Calculation32::roundWithStep( + const sass::string& strategy, + Number* number, Number* step) + { + + // std::cerr << "roundWithStep " << step->value() << "\n"; + + if (std::isinf(step->value())) { + // std::cerr << "Step is infinite\n"; + if (number->value() == 0) { + return number; + } + else if (std::isinf(number->value())) { + return _matchUnits(NaN, number); + } + else if (strategy == str_up) { + return number->value() > 0 + ? _matchUnits(std::numeric_limits::infinity(), number) + : _matchUnits(-0.0, number); + } + else if (strategy == str_down) { + return number->value() < 0 + ? _matchUnits(-std::numeric_limits::infinity(), number) + : _matchUnits(0.0, number); + } + else if (strategy == str_nearest || strategy == str_to_zero) { + // std::cerr << "Return from match units\n"; + if (std::isinf(number->value())) { + return _matchUnits(-NaN, number); + } + return number->value() > 0 + ? _matchUnits(0.0, number) + : _matchUnits(-0.0, number); + } + else { + std::cerr << "############ NADADAD\n"; + std::cerr << "############ NADADAD\n"; + std::cerr << "############ NADADAD\n"; + } + } + + auto factor = step->getUnitConversionFactor(number); + double stepWithNumberUnit = step->value() * factor; + + if (strategy == str_nearest) { + return _matchUnits(std::round( + number->value() / stepWithNumberUnit + ) * stepWithNumberUnit, number); + } + else if (strategy == str_up) { + return _matchUnits((step->value() < 0 + ? std::floor(number->value() / stepWithNumberUnit) + : std::ceil(number->value() / stepWithNumberUnit) + ) * stepWithNumberUnit, number); + } + else if (strategy == str_down) { + return _matchUnits((step->value() < 0 + ? std::ceil(number->value() / stepWithNumberUnit) + : std::floor(number->value() / stepWithNumberUnit) + ) * stepWithNumberUnit, number); + } + else if (strategy == str_to_zero) { + return _matchUnits((number->value() < 0 + ? std::ceil(number->value() / stepWithNumberUnit) + : std::floor(number->value() / stepWithNumberUnit) + ) * stepWithNumberUnit, number); + } + // This should not be reached, strategy is checked before + return SASS_MEMORY_NEW(Number, number->pstate(), 1442.1442); + } + + + /// Verifies that all the numbers in [args] aren't known to be incompatible + /// with one another, and that they don't have units that are too complex for + /// calculations. + void Eval::_verifyCompatibleNumbers(sass::vector args, const SourceSpan& pstate) { + _verifyCompatibleNumbers2(logger, pstate, args, true); + } + + + Value* Eval::operateInternal( + const SourceSpan& pstate, SassOperator op, + AstNode* left, AstNode* right, + bool inLegacySassFunction, bool simplify) + { + // if (!simplify) return CalculationOperation._(operator, left, right); + + if (!simplify) { + return SASS_MEMORY_NEW(CalcOperation, + pstate, op, left, right); + } + + //debug_ast(left, "left: "); + //debug_ast(right, "right: "); + + AstNodeObj lhs = left->simplify(logger); + AstNodeObj rhs = right->simplify(logger); + + + Number* lnr = dynamic_cast(lhs.ptr()); + Number* rnr = dynamic_cast(rhs.ptr()); + + if (op == ADD || op == SUB) { + if (lnr != nullptr && rnr != nullptr) { + if (inLegacySassFunction) + { + if (lnr->canCompareTo(rnr, false)) { + return op == ADD + ? lnr->plus(rnr, logger, pstate) + : lnr->minus(rnr, logger, pstate); + } + } + else + { + if (lnr->hasCompatibleUnits(rnr, true)) { + return op == ADD + ? lnr->plus(rnr, logger, pstate) + : lnr->minus(rnr, logger, pstate); + } + } + // inLegacySassFunction + // ? left.isComparableTo(right) + // : left.hasCompatibleUnits(right)) + + // if (inLegacySassFunction + // ? left.isComparableTo(right) + // : left.hasCompatibleUnits(right)) { + // return operator == CalculationOperator.plus + // ? left.plus(right) + // : left.minus(right); + // } + } + + // std::cerr << "VERIFY NUMBERS " << inLegacySassFunction << "\n"; + _verifyCompatibleNumbers2(logger, pstate, { left, right }, true); + // std::cerr << "Numbers are verified\n"; + + + return SASS_MEMORY_NEW(CalcOperation, + pstate, op, lhs.ptr(), rhs.ptr()); + + + } + else if (lnr != nullptr && rnr != nullptr) { + return op == MUL + ? lnr->times(rnr, logger, pstate) + : lnr->dividedBy(rnr, logger, pstate); + } + else { + return SASS_MEMORY_NEW(CalcOperation, + pstate, op, lhs.ptr(), rhs.ptr()); + throw std::runtime_error("Not Implemented 33"); + } + // + } + + + /* + + + static Object operateInternal( + CalculationOperator operator, Object left, Object right, + {required bool inLegacySassFunction, required bool simplify}) { + + if (!simplify) return CalculationOperation._(operator, left, right); + left = _simplify(left); + right = _simplify(right); + + if (operator case CalculationOperator.plus || CalculationOperator.minus) { + if (left is SassNumber && + right is SassNumber && + (inLegacySassFunction + ? left.isComparableTo(right) + : left.hasCompatibleUnits(right))) { + return operator == CalculationOperator.plus + ? left.plus(right) + : left.minus(right); + } + + _verifyCompatibleNumbers([left, right]); + + if (right is SassNumber && number_lib.fuzzyLessThan(right.value, 0)) { + right = right.times(SassNumber(-1)); + operator = operator == CalculationOperator.plus + ? CalculationOperator.minus + : CalculationOperator.plus; + } + + return CalculationOperation._(operator, left, right); + } else if (left is SassNumber && right is SassNumber) { + return operator == CalculationOperator.times + ? left.times(right) + : left.dividedBy(right); + } else { + return CalculationOperation._(operator, left, right); + } + } + */ + + // Creates a `round()` calculation with the given [strategyOrNumber], + // [numberOrStep], and [step]. Strategy must be either nearest, + // up, down or to-zero. + // + // Number and step must be either a [SassNumber], a [SassCalculation], + // an unquoted [SassString], or a [CalculationOperation]. + // + // This automatically simplifies the calculation, so it may return a + // [SassNumber] rather than a [SassCalculation]. It throws an exception if it + // can determine that the calculation will definitely produce invalid CSS. + // + // This may be passed fewer than two arguments, but only if one of the + // arguments is an unquoted `var()` string. + + + + + Number* Calculation32::fnAbs(Logger& logger, const FunctionExpression* pstate, AstNode* argument, Number* arg) + { + return SASS_MEMORY_NEW(Number, arg->pstate(), std::abs(arg->value()), arg); + } + + Number* Calculation32::fnExp(Logger& logger, const FunctionExpression* pstate, AstNode* argument, Number* arg) + { + return SASS_MEMORY_NEW(Number, arg->pstate(), std::exp(arg->value()), arg); + } + + Number* Calculation32::fnSign(Logger& logger, const FunctionExpression* pstate, AstNode* argument, Number* arg) + { + return SASS_MEMORY_NEW(Number, arg->pstate(), arg->value() < 0 ? -1 : arg->value() > 0 ? 1 : 0, arg); + } + + Value* Calculation32::calc_abs(Logger& logger, const FunctionExpression* pstate, AstNode* argument) + { + return singleArgument(logger, pstate, str_abs, argument, fnAbs, true); + } + + Value* Calculation32::calc_exp(Logger& logger, const FunctionExpression* pstate, AstNode* argument) + { + return singleArgument(logger, pstate, str_exp, argument, fnExp, true); + } + + Value* Calculation32::calc_sign(Logger& logger, const FunctionExpression* pstate, AstNode* argument) + { + return singleArgument(logger, pstate, str_sign, argument, fnSign, true); + } + + + Number* Calculation32::fnMin(Logger& logger, const FunctionExpression* pstate, AstNode* argument, Number* arg) + { + return SASS_MEMORY_NEW(Number, arg->pstate(), std::exp(arg->value()), arg); + } + + Number* Calculation32::fnMax(Logger& logger, const FunctionExpression* pstate, AstNode* argument, Number* arg) + { + return SASS_MEMORY_NEW(Number, arg->pstate(), std::exp(arg->value()), arg); + } + + + + + /// Creates a `pow()` calculation with the given [base] and [exponent]. + /// + /// Each argument must be either a [SassNumber], a [SassCalculation], an + /// unquoted [SassString], or a [CalculationOperation]. + /// + /// This automatically simplifies the calculation, so it may return a + /// [SassNumber] rather than a [SassCalculation]. It throws an exception if it + /// can determine that the calculation will definitely produce invalid CSS. + /// + /// This may be passed fewer than two arguments, but only if one of the + /// arguments is an unquoted `var()` string. + Value* Calculation32::calc_pow(Logger& logger, const FunctionExpression* pstate, AstNode* arg1, AstNode* arg2) + { + AstNodeObj base = arg1->simplify(logger); + AstNodeObj exponent = nullptr; + if (arg2) exponent = arg2->simplify(logger); + Number* nr_base = Cast(base); + Number* nr_exp = Cast(exponent); + if (nr_base && nr_exp) { + return SASS_MEMORY_NEW(Number, pstate->pstate(), + std::pow(nr_base->value(), nr_exp->value())); + } + // Otherwise return calculation literal + return SASS_MEMORY_NEW(Calculation, + pstate->pstate(), str_pow, + { base, exponent }); + } + + /// Creates a `log()` calculation with the given [number] and [base]. + /// + /// Each argument must be either a [SassNumber], a [SassCalculation], an + /// unquoted [SassString], or a [CalculationOperation]. + /// + /// This automatically simplifies the calculation, so it may return a + /// [SassNumber] rather than a [SassCalculation]. It throws an exception if it + /// can determine that the calculation will definitely produce invalid CSS. + /// + /// If arguments contains exactly a single argument, the base is set to + /// `math.e` by default. + Value* Calculation32::calc_log(Logger& logger, const FunctionExpression* pstate, AstNode* number, AstNode* base) + { + AstNodeObj arg_nr = number->simplify(logger); + AstNodeObj arg_base = base ? base->simplify(logger) : nullptr; + if (Number* nr_value = Cast(arg_nr)) { + nr_value->assertNoUnits(logger, str_number); + if (arg_base == nullptr) { + return SASS_MEMORY_NEW(Number, pstate->pstate(), + std::log(nr_value->value())); // Regular log + } + if (Number* nr_base = Cast(arg_base)) { + nr_base->assertNoUnits(logger, str_base); + return SASS_MEMORY_NEW(Number, pstate->pstate(), + std::log(nr_value->value()) / std::log(nr_base->value())); + } + } + // Otherwise return calc literal + return SASS_MEMORY_NEW(Calculation, + pstate->pstate(), str_log, + { arg_nr, arg_base }); + } + // EO calc_log + + + + + /// Creates a `atan2()` calculation for [y] and [x]. + /// + /// Each argument must be either a [SassNumber], a [SassCalculation], an + /// unquoted [SassString], or a [CalculationOperation]. + /// + /// This automatically simplifies the calculation, so it may return a + /// [SassNumber] rather than a [SassCalculation]. It throws an exception if it + /// can determine that the calculation will definitely produce invalid CSS. + /// + /// This may be passed fewer than two arguments, but only if one of the + /// arguments is an unquoted `var()` string. + Value* Calculation32::calc_atan2(Logger& logger, const SourceSpan& pstate, AstNode* y, AstNode* x) + { + AstNodeObj arg_y = y->simplify(logger); + AstNodeObj arg_x = x ? x->simplify(logger) : nullptr; + // _verifyLength(args, 2); + if (Number* nr_y = Cast(arg_y)) { + if (!nr_y->isValidCssUnit()) throw Exception::IncompatibleCalcValue(logger, *nr_y, y->pstate()); + if (Number* nr_x = Cast(arg_x)) { + if (!nr_x->isValidCssUnit()) throw Exception::IncompatibleCalcValue(logger, *nr_x, x->pstate()); + if (!(unit_percent == nr_y || unit_percent == nr_x)) { + double factor = nr_x->getUnitConversionFactor(nr_y); + if (factor != 0) return SASS_MEMORY_NEW(Number, pstate, + std::atan2(nr_y->value(), nr_x->value() * factor) + * Constants::Math::RAD_TO_DEG, unit_deg); + } + } + } + // Otherwise return calc literal + return SASS_MEMORY_NEW(Calculation, + pstate, str_atan2, { arg_y, arg_x }); + } + // EO calc_atan2 + + + Value* Calculation32::calc_round(Logger& logger, Expression* node, const ValueVector& arguments) + { + // std::cerr << "CALCULATE ROUND\n"; + + if (arguments.size() == 0) + { + throw Exception::MissingArgument(logger, str_number); + } + else if (arguments.size() == 1) + { + AstNodeObj arg_0 = arguments[0]->simplify(logger); + if (auto nr_number = dynamic_cast(arg_0.ptr())) { + return _matchUnits(std::round(nr_number->value()), nr_number); + } + if (auto str_number = dynamic_cast(arg_0.ptr())) { + return SASS_MEMORY_NEW(Calculation, node->pstate(), str_round, { str_number }); + } + callStackFrame frame(logger, arg_0->pstate()); + throw Exception::SassScriptException("Single argument " + + arg_0->toString() + " expected to be simplifiable.", + logger, arg_0->pstate() + ); + + } + else if (arguments.size() == 2) + { + AstNodeObj arg_0 = arguments[0]->simplify(logger); + AstNodeObj arg_1 = arguments[1]->simplify(logger); + if (auto nr_number = dynamic_cast(arg_0.ptr())) { + if (auto nr_step = dynamic_cast(arg_1.ptr())) { + _verifyCompatibleNumbers2(logger, + node->pstate(), { nr_number, nr_step }); + if (nr_number->hasCompatibleUnits(nr_step, true)) { + return roundWithStep(str_nearest, nr_number, nr_step); + } + else { + return new Calculation(node->pstate(), + "round", { arg_0, arg_1 }); + } + } + } + if (String* strategy = dynamic_cast(arg_0.ptr())) + { + const sass::string& method(strategy->value()); + if (method == str_nearest || method == str_up || + method == str_to_zero || method == str_down) + { + if (String* str_number = dynamic_cast(arg_1.ptr())) { + if (str_number->isVar()) { + return SASS_MEMORY_NEW(Calculation, node->pstate(), + str_round, { arguments[0].ptr(), arguments[1].ptr() }); + } + } + throw Exception::SassScriptException(logger, node->pstate(), + "If strategy is not null, step is required."); + } + } + return SASS_MEMORY_NEW(Calculation, node->pstate(), + str_round, { arguments[0].ptr(), arguments[1].ptr() }); + } + else if (arguments.size() == 3) + { + AstNodeObj arg_0 = arguments[0]->simplify(logger); + AstNodeObj arg_1 = arguments[1]->simplify(logger); + AstNodeObj arg_2 = arguments[2]->simplify(logger); + if (String* strategy = dynamic_cast(arg_0.ptr())) + { + const sass::string& method(strategy->value()); + if (method == str_nearest || method == str_up || + method == str_to_zero || method == str_down) + { + auto nr_number = dynamic_cast(arg_1.ptr()); + auto nr_step = dynamic_cast(arg_2.ptr()); + + if (nr_number != nullptr && nr_step != nullptr) { + + if (nr_number->hasCompatibleUnits(nr_step)) { + // std::cerr << "valid strategy " << method << "\n"; + return roundWithStep(method, nr_number, nr_step); + // return SASS_MEMORY_NEW(String, node->pstate(), "TODO 3"); + } + else { + return new Calculation(node->pstate(), + "round", { arg_0, arg_1, arg_2 }); + } + + } + else if (dynamic_cast(arg_1.ptr())) { + return new Calculation(node->pstate(), + "round", { arg_0, arg_1, arg_2 }); + } + else if (nr_step == nullptr) { + return new Calculation(node->pstate(), + "round", { arg_0, arg_1, arg_2 }); + } + else if (/* String* rest = */ dynamic_cast(arg_2.ptr())) { + return new Calculation(node->pstate(), + "round", { arg_0, arg_1, arg_2 }); + } + else if (nr_number == nullptr) { + return new Calculation(node->pstate(), + "round", { arg_0, arg_1, arg_2 }); + } + else { + throw Exception::SassScriptException(logger, node->pstate(), + "If strategy is not null, step is required."); + } + } + else if (strategy->isVar()) { + return new Calculation(node->pstate(), + "round", { arg_0, arg_1, arg_2 }); + } + else if (String* rest = arguments[0]->isaString()) { + return new Calculation(node->pstate(), + "round", { rest, arg_1, arg_2 }); + } + else { + callStackFrame frame(logger, strategy->pstate()); + throw Exception::SassScriptException(method + + " must be either nearest, up, down or to-zero.", + logger, strategy->pstate()); + } + } + else if (arguments[0] != nullptr) { + callStackFrame frame(logger, arguments[0]->pstate()); + throw Exception::SassScriptException(arguments[0]->toCss() + + " must be either nearest, up, down or to-zero.", + logger, arguments[0]->pstate()); + } + else { + // Shouldn't happen, but play safe + throw Exception::MissingArgument( + logger, str_number); + } + } + else { + throw Exception::TooManyArguments( + logger, arguments.size(), 3); + } + + } + + Value* Calculation32::calc_mod(Logger& logger, const FunctionExpression* pstate, AstNode* lhs, AstNode* rhs) + { + auto dividend = lhs->simplify(logger); + auto modulus = rhs ? rhs->simplify(logger) : nullptr; + // return singleArgument(logger, str_tan, argument, fnTan, true); + if (auto dividend_nr = dynamic_cast(dividend)) { + if (auto modulus_nr = dynamic_cast(modulus)) { + // check compatible units + return dividend_nr->modulo(modulus_nr, logger, lhs->pstate()); + } + } + return SASS_MEMORY_NEW(Calculation, lhs->pstate(), "mod", { dividend, modulus }); + } + + Value* Calculation32::calc_rem(Logger& logger, const FunctionExpression* pstate, AstNode* lhs, AstNode* rhs) + { + auto dividend = lhs->simplify(logger); + auto modulus = rhs ? rhs->simplify(logger) : nullptr; + // return singleArgument(logger, str_tan, argument, fnTan, true); + if (auto dividend_nr = dynamic_cast(dividend)) { + if (auto modulus_nr = dynamic_cast(modulus)) { + // check compatible units + return dividend_nr->remainder(modulus_nr, logger, lhs->pstate()); + } + } + return SASS_MEMORY_NEW(Calculation, lhs->pstate(), "mod", { dividend, modulus }); + } + + + /* + + static Value round(Object strategyOrNumber, + [Object? numberOrStep, Object? step]) { + switch (( + _simplify(strategyOrNumber), + numberOrStep.andThen(_simplify), + step.andThen(_simplify) + )) { + case (SassNumber number, null, null): + return _matchUnits(number.value.round().toDouble(), number); + + case (SassNumber number, SassNumber step, null) + when !number.hasCompatibleUnits(step): + _verifyCompatibleNumbers([number, step]); + return SassCalculation._("round", [number, step]); + + case (SassNumber number, SassNumber step, null): + _verifyCompatibleNumbers([number, step]); + return _roundWithStep('nearest', number, step); + + case ( + SassString(text: 'nearest' || 'up' || 'down' || 'to-zero') && + var strategy, + SassNumber number, + SassNumber step + ) + when !number.hasCompatibleUnits(step): + _verifyCompatibleNumbers([number, step]); + return SassCalculation._("round", [strategy, number, step]); + + case ( + SassString(text: 'nearest' || 'up' || 'down' || 'to-zero') && + var strategy, + SassNumber number, + SassNumber step + ): + _verifyCompatibleNumbers([number, step]); + return _roundWithStep(strategy.text, number, step); + + case ( + SassString(text: 'nearest' || 'up' || 'down' || 'to-zero') && + var strategy, + SassString rest, + null + ): + return SassCalculation._("round", [strategy, rest]); + + case ( + SassString(text: 'nearest' || 'up' || 'down' || 'to-zero'), + _?, + null + ): + throw SassScriptException("If strategy is not null, step is required."); + + case ( + SassString(text: 'nearest' || 'up' || 'down' || 'to-zero'), + null, + null + ): + throw SassScriptException( + "Number to round and step arguments are required."); + + case (SassString rest, null, null): + return SassCalculation._("round", [rest]); + + case (var number, null, null): + throw SassScriptException( + "Single argument $number expected to be simplifiable."); + + case (var number, var step?, null): + return SassCalculation._("round", [number, step]); + + case ( + (SassString(text: 'nearest' || 'up' || 'down' || 'to-zero') || + SassString(isVar: true)) && + var strategy, + var number?, + var step? + ): + return SassCalculation._("round", [strategy, number, step]); + + case (_, _?, _?): + throw SassScriptException( + "$strategyOrNumber must be either nearest, up, down or to-zero."); + + case (_, null, _?): + // TODO(pamelalozano): Get rid of this case once dart-lang/sdk#52908 is solved. + // ignore: unreachable_switch_case + case (_, _, _): + throw SassScriptException("Invalid parameters."); + } + } +*/ + + /* + + // Returns [value] coerced to [number]'s units. + static SassNumber _matchUnits(double value, SassNumber number) => + SassNumber.withUnits(value, + numeratorUnits: number.numeratorUnits, + denominatorUnits: number.denominatorUnits); + + /// Returns a rounded [number] based on a selected rounding [strategy], + /// to the nearest integer multiple of [step]. + static SassNumber _roundWithStep( + String strategy, SassNumber number, SassNumber step) { + if (!{'nearest', 'up', 'down', 'to-zero'}.contains(strategy)) { + throw ArgumentError( + "$strategy must be either nearest, up, down or to-zero."); + } + + if (number.value.isInfinite && step.value.isInfinite || + step.value == 0 || + number.value.isNaN || + step.value.isNaN) { + return _matchUnits(double.nan, number); + } + if (number.value.isInfinite) return number; + + if (step.value.isInfinite) { + return switch ((strategy, number.value)) { + (_, 0) => number, + ('nearest' || 'to-zero', > 0) => _matchUnits(0.0, number), + ('nearest' || 'to-zero', _) => _matchUnits(-0.0, number), + ('up', > 0) => _matchUnits(double.infinity, number), + ('up', _) => _matchUnits(-0.0, number), + ('down', < 0) => _matchUnits(-double.infinity, number), + ('down', _) => _matchUnits(0, number), + (_, _) => throw UnsupportedError("Invalid argument: $strategy.") + }; + } + + var stepWithNumberUnit = step.convertValueToMatch(number); + return switch (strategy) { + 'nearest' => _matchUnits( + (number.value / stepWithNumberUnit).round() * stepWithNumberUnit, + number), + 'up' => _matchUnits( + (step.value < 0 + ? (number.value / stepWithNumberUnit).floor() + : (number.value / stepWithNumberUnit).ceil()) * + stepWithNumberUnit, + number), + 'down' => _matchUnits( + (step.value < 0 + ? (number.value / stepWithNumberUnit).ceil() + : (number.value / stepWithNumberUnit).floor()) * + stepWithNumberUnit, + number), + 'to-zero' => number.value < 0 + ? _matchUnits( + (number.value / stepWithNumberUnit).ceil() * stepWithNumberUnit, + number) + : _matchUnits( + (number.value / stepWithNumberUnit).floor() * stepWithNumberUnit, + number), + _ => _matchUnits(double.nan, number) + }; + } + + */ + +} diff --git a/src/calculation.hpp b/src/calculation.hpp new file mode 100644 index 0000000000..0ccb82b16f --- /dev/null +++ b/src/calculation.hpp @@ -0,0 +1,222 @@ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#ifndef SASS_CALCULATION_HPP +#define SASS_CALCULATION_HPP + +#include "ast.hpp" +#include "strings.hpp" +#include "exceptions.hpp" + +#include "debugger.hpp" + +#include + +namespace Sass { + + class Calculation32 { + + sass::string name; + + sass::vector arguments; + + bool isSpecialNumber = true; + + public: + + static Calculation32 unsimplified(const sass::string& name, sass::vector arguments) { + return Calculation32(name, arguments); + } + + static Number* roundWithStep(const sass::string&, Number* number, Number* step); + + static Value* singleArgument(Logger& logger, const FunctionExpression* pstate, const sass::string& name, AstNode* argument, Number*(*mathFunc)(Logger& logger, const FunctionExpression* pstate, AstNode* argument, Number*), bool forbitUnits = false) + { + AstNode* simplified = argument->simplify(logger); + auto* number = dynamic_cast(simplified); + if (number == nullptr) return SASS_MEMORY_NEW( + Calculation, argument->pstate(), name, { simplified }); + // if (forbitUnits) number->assertNoUnits(); + return mathFunc(logger, pstate, argument, number); + } + + static Value* singleArgument2(Logger& logger, const SourceSpan& pstate, const sass::string& name, const ValueVector& args, Number* (*mathFunc)(Logger& logger, const SourceSpan& pstate, AstNode* argument, Number*), bool forbitUnits = false) + { + AstNode* simplified = args[0]->simplify(logger); + auto* number = dynamic_cast(simplified); + if (number == nullptr) return SASS_MEMORY_NEW( + Calculation, pstate, name, {simplified}); + // if (forbitUnits) number->assertNoUnits(); + return mathFunc(logger, pstate, args[0], number); + } + + // No support for numbers with units + static Number* fnSqrt(Logger& logger, const FunctionExpression* pstate, AstNode* argument, Number* arg) { + double rv = std::sqrt(arg->value()); + return SASS_MEMORY_NEW(Number, arg->pstate(), rv); + } + + static Number* fnSin(Logger& logger, const FunctionExpression* pstate, AstNode* argument, Number* arg) { + double factor = arg->factorToUnits(unit_rad); + if (factor == 0.0) { + throw Exception::SassScriptException( + "$number: Expected " + argument->toString() + " to have an angle unit (deg, grad, rad, turn).", + logger, pstate->pstate()); + } + return SASS_MEMORY_NEW(Number, arg->pstate(), + std::sin(arg->value() * factor)); + } + + static Number* fnCos(Logger& logger, const FunctionExpression* pstate, AstNode* argument, Number* arg) { + double factor = arg->factorToUnits(unit_rad); + if (factor == 0.0) { + throw Exception::SassScriptException( + "$number: Expected " + argument->toString() + " to have an angle unit (deg, grad, rad, turn).", + logger, pstate->pstate()); + } + return SASS_MEMORY_NEW(Number, arg->pstate(), + std::cos(arg->value() * factor)); + } + + static Number* fnTan(Logger& logger, const FunctionExpression* pstate, AstNode* argument, Number* arg) { + double factor = arg->factorToUnits(unit_rad); + if (factor == 0.0) { + throw Exception::SassScriptException( + "$number: Expected " + argument->toString() + " to have an angle unit (deg, grad, rad, turn).", + logger, pstate->pstate()); + } + return SASS_MEMORY_NEW(Number, arg->pstate(), + std::tan(arg->value() * factor)); + } + + + + static Value* calc_sign2(Logger& logger, const SourceSpan& pstate, const ValueVector& args); + + static Value* calc_exp2(Logger& logger, const SourceSpan& pstate, const ValueVector& args); + + static Value* calc_sqrt(Logger& logger, const SourceSpan& pstate, const ValueVector& args); + + static Value* calc_abs(Logger& logger, const SourceSpan& pstate, const ValueVector& args); + + static Value* calc_sin(Logger& logger, const SourceSpan& pstate, const ValueVector& args); + static Value* calc_cos(Logger& logger, const SourceSpan& pstate, const ValueVector& args); + static Value* calc_tan(Logger& logger, const SourceSpan& pstate, const ValueVector& argument); + + static Value* calc_asin(Logger& logger, const SourceSpan& pstate, const ValueVector& args); + static Value* calc_acos(Logger& logger, const SourceSpan& pstate, const ValueVector& args); + static Value* calc_atan(Logger& logger, const SourceSpan& pstate, const ValueVector& argument); + + static Value* calc_min(Logger& logger, const SourceSpan& pstate, const ValueVector& args); + static Value* calc_max(Logger& logger, const SourceSpan& pstate, const ValueVector& args); + + static Value* calc_clamp(Logger& logger, const SourceSpan& pstate, const ValueVector& args); + static Value* calc_hypot(Logger& logger, const SourceSpan& pstate, const ValueVector& args); + + static Value* calc_pow2(Logger& logger, const SourceSpan& pstate, const ValueVector& args); + static Value* calc_mod2(Logger& logger, const SourceSpan& pstate, const ValueVector& args); + static Value* calc_rem2(Logger& logger, const SourceSpan& pstate, const ValueVector& args); + static Value* calc_atan3(Logger& logger, const SourceSpan& pstate, const ValueVector& args); + + + static Number* fnAbs(Logger& logger, const FunctionExpression* pstate, AstNode* argument, Number* arg); + static Number* fnExp(Logger& logger, const FunctionExpression* pstate, AstNode* argument, Number* arg); + static Number* fnSign(Logger& logger, const FunctionExpression* pstate, AstNode* argument, Number* arg); + static Value* calc_abs(Logger& logger, const FunctionExpression* pstate, AstNode* argument); + static Value* calc_exp(Logger& logger, const FunctionExpression* pstate, AstNode* argument); + static Value* calc_sign(Logger& logger, const FunctionExpression* pstate, AstNode* argument); + + static Number* fnMin(Logger& logger, const FunctionExpression* pstate, AstNode* argument, Number* arg); + static Number* fnMax(Logger& logger, const FunctionExpression* pstate, AstNode* argument, Number* arg); + + static Value* calc_pow(Logger& logger, const FunctionExpression* pstate, AstNode* arg1, AstNode* arg2); + static Value* calc_atan2(Logger& logger, const SourceSpan& pstate, AstNode* arg1, AstNode* arg2); + static Value* calc_log(Logger& logger, const FunctionExpression* pstate, AstNode* arg1, AstNode* arg2); + + static Value* calc_sqrt(Logger& logger, const FunctionExpression* pstate, AstNode* argument) { + return singleArgument(logger, pstate, str_sqrt, argument, fnSqrt, true); + } + + + // static Value* calc_sin(Logger& logger, const FunctionExpression* pstate, AstNode* argument) { + // return singleArgument(logger, pstate, str_sin, argument, fnSin, true); + // } + // + // static Value* calc_cos(Logger& logger, const FunctionExpression* pstate, AstNode* argument) { + // return singleArgument(logger, pstate, str_cos, argument, fnCos, true); + // } + // static Value* calc_tan(Logger& logger, const FunctionExpression* pstate, AstNode* argument) { + // return singleArgument(logger, pstate, str_tan, argument, fnTan, true); + // } + + static Value* calc_round(Logger& logger, Expression* node, const ValueVector& arguments); + + + static Value* calc_rem(Logger& logger, const FunctionExpression* pstate, AstNode* lhs, AstNode* rhs); + + static Value* calc_mod(Logger& logger, const FunctionExpression* pstate, AstNode* lhs, AstNode* rhs); + + static Value* calc_fn(Logger& logger, AstNode* argument) { + AstNodeObj simplified = argument->simplify(logger); + // debug_ast(simplified, "simplified: "); + if (auto number = dynamic_cast(simplified.ptr())) { + simplified.detach(); return number; + } + else if (auto calc = dynamic_cast(simplified.ptr())) { + simplified.detach(); return calc; + } + else { + return SASS_MEMORY_NEW(Calculation, + argument->pstate(), "calc", { simplified }); + } + } + + + + private: + + // Internal constructor that doesn't perform any validation or simplification. + Calculation32(const sass::string& name, sass::vector arguments) : + name(name), arguments(arguments) { } + + /* + /// Simplifies a calculation argument. + static AstNode* _simplify(AstNode* arg) { + return nullptr; + // if (dynamic_cast(arg)) {} + // else if (dynamic_cast(arg)) {} + // //else if (dynamic_cast(arg)) {} + // //else if (dynamic_cast(arg)) {} + // else if (dynamic_cast(arg)) {} + // else if (dynamic_cast(arg)) {} + // else if (dynamic_cast(arg)) {} + + } + */ + /* + = > switch (arg) { + SassNumber() || CalculationOperation() = > arg, + CalculationInterpolation() = > + SassString('(${arg.value})', quotes: false), + SassString(hasQuotes: false) = > arg, + SassString() = > throw SassScriptException( + "Quoted string $arg can't be used in a calculation."), + SassCalculation( + name: 'calc', + arguments : [SassString(hasQuotes:false, : var text)] + ) + when _needsParentheses(text) = > + SassString('($text)', quotes: false), + SassCalculation(name: 'calc', arguments : [var value] ) = > value, + SassCalculation() = > arg, + Value() = > throw SassScriptException( + "Value $arg can't be used in a calculation."), + _ = > throw ArgumentError("Unexpected calculation argument $arg.") + }; + */ + + }; + +} + +#endif diff --git a/src/callstack.hpp b/src/callstack.hpp new file mode 100644 index 0000000000..9980000125 --- /dev/null +++ b/src/callstack.hpp @@ -0,0 +1,64 @@ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#ifndef SASS_CALLSTACK_HPP +#define SASS_CALLSTACK_HPP + +// sass.hpp must go before all system headers +// to get the __EXTENSIONS__ fix on Solaris. +#include "capi_sass.hpp" + +#include "backtrace.hpp" + +namespace Sass { + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // Utility class to add a frame onto a call-stack (RAII). + // Cleans up automatically when object goes out of scope. + // We assume this happens in well defined order, as + // we do not check if we actually remove ourself! + // ToDo: rename to callTrace + class callStackFrame { + + private: + + // The shared callStack + BackTraces& backTraces; + + // The current stack frame + BackTrace frame; + + // Are we invoked by `call` + bool viaCall; + + public: + + // Create object and add frame to stack + callStackFrame(BackTraces& backTraces, + const BackTrace& frame, + bool viaCall = false) : + backTraces(backTraces), + frame(frame), + viaCall(viaCall) + { + // Append frame to stack + if (!viaCall) backTraces.push_back(frame); + } + + // Remove frame from stack on destruction + ~callStackFrame() + { + // Pop frame from stack + if (!viaCall) backTraces.pop_back(); + } + + }; + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + +} + +#endif diff --git a/src/capi_compiler.cpp b/src/capi_compiler.cpp new file mode 100644 index 0000000000..a398d8df8a --- /dev/null +++ b/src/capi_compiler.cpp @@ -0,0 +1,758 @@ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#include "capi_compiler.hpp" + +#include +#include +#include "logger.hpp" +#include "compiler.hpp" +#include "exceptions.hpp" + +namespace Sass { + + ///////////////////////////////////////////////////////////////////////// + // Some specific implementations that could also go into C++ part + // ToDo: maybe we should guard all C-API functions this way!? + ///////////////////////////////////////////////////////////////////////// + + // Promote and format error onto compiler with given status, message and traces + int handle_error(Compiler& compiler, int status, const char* what, StackTraces* traces) + { + + sass::ostream error; + bool has_final_lf = false; + Logger& logger(compiler); + error << "Error: "; + // Add message and ensure it is + // added with a final line-feed. + const char* msg = what; + if (msg != nullptr) { + error << msg; + while (*msg) { + has_final_lf = + *msg == '\r' || + *msg == '\n'; + ++msg; + } + if (!has_final_lf) { + error << STRMLF; + } + } + + sass::ostream formatted; + print_wrapped(error.str(), compiler.columns, formatted); + + // Clear the previous array + compiler.error.traces.clear(); + // Some stuff is only logged if we have some traces + // Otherwise we don't know where the error comes from + if (traces && traces->size() > 0) { + // Write traces to string with some indentation + logger.writeStackTraces(formatted, *traces, " "); + // Copy items over to error object + compiler.error.traces = *traces; + } + + // Attach stuff to error object + compiler.error.what.clear(); + compiler.error.status = status; + if (what) compiler.error.what = what; + compiler.error.formatted = formatted.str(); + compiler.state = SASS_COMPILER_FAILED; + + // Return status again + return status; + } + // EO handle_error + + // Main entry point to catch errors + static int handle_error(Compiler& compiler) + { + // Re-throw last error + try { throw; } + // Catch LibSass specific error cases + catch (Exception::Base & e) { handle_error(compiler, 1, e.what(), &e.traces); } + // Bad allocations can always happen, maybe we should exit in this case? + catch (std::bad_alloc & e) { handle_error(compiler, 2, e.what()); } + // Other errors should not really happen and indicate more severe issues! + catch (std::exception & e) { handle_error(compiler, 3, e.what()); } + catch (sass::string & e) { handle_error(compiler, 4, e.c_str()); } + catch (const char* what) { handle_error(compiler, 4, what); } + catch (...) { handle_error(compiler, 5, "unknown"); } + // Return the error state + return compiler.error.status; + } + // EO handle_error + + // allow one error handler to throw another error + // this can happen with invalid utf8 and json lib + // ToDo: this might be obsolete but doesn't hurt!? + static int handle_errors(Compiler& compiler) + { + try { return handle_error(compiler); } + catch (...) { return handle_error(compiler); } + } + // EO handle_errors + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // Main implementation (caller is wrapped in try/catch) + void _sass_compiler_parse(Compiler& compiler) + { + compiler.parse(); + } + // EO _sass_compiler_parse + + // Main implementation (caller is wrapped in try/catch) + void _sass_compiler_compile(Compiler& compiler) + { + compiler.compile(); + } + // EO _sass_compiler_compile + + // Main implementation (caller is wrapped in try/catch) + void _sass_compiler_render(Compiler& compiler) + { + + // Bail out if we had any previous errors + if (compiler.error.status != 0) return; + // Make sure compile step was called before + if (compiler.compiled == nullptr) return; + + // This will hopefully use move semantics + OutputBuffer output(compiler.renderCss()); + compiler.content = std::move(output.buffer); + + // Create options to render source map and footer. + SrcMapOptions& options(compiler.mapopt); + // Deduct some options always from original values. + // ToDo: is there really any need to customize this? + if (options.origin.empty() || options.origin == "stream://stdout") { + options.origin = compiler.getOutputPath(); + } + if (options.path.empty() || options.path == "stream://stdout") { + if (!options.origin.empty() && options.origin != "stream://stdout") { + options.path = options.origin + ".map"; + } + } + + switch (options.mode) { + case SASS_SRCMAP_NONE: + compiler.srcmap = nullptr; + compiler.footer = nullptr; + break; + case SASS_SRCMAP_CREATE: + compiler.srcmap = compiler.renderSrcMapJson(*output.srcmap); + compiler.footer = nullptr; // Don't add link, just create map file + break; + case SASS_SRCMAP_EMBED_LINK: + compiler.srcmap = compiler.renderSrcMapJson(*output.srcmap); + compiler.footer = compiler.renderSrcMapLink(*output.srcmap); + break; + case SASS_SRCMAP_EMBED_JSON: + compiler.srcmap = compiler.renderSrcMapJson(*output.srcmap); + compiler.footer = compiler.renderEmbeddedSrcMap(*output.srcmap); + break; + } + + } + // EO _sass_compiler_render + + // Main implementation (caller is wrapped in try/catch) + void _sass_compiler_write_output(Compiler& compiler) + { + + const char* path = compiler.output_path.c_str(); + + // Write regular output if no error occurred + if (compiler.error.status == 0) { + + // ToDo: can we use the same types? + const char* footer = compiler.footer; + const char* content = compiler.content.empty() ? + nullptr : compiler.content.c_str(); + + // Check if anything is to write + if (content || footer) { + // Check where to write it to + if (path && compiler.hasOutputFile()) { + std::ofstream fh(path, std::ios::out | std::ios::binary); + if (!fh) throw Exception::IoError(compiler, + "Error opening output file", + File::abs2rel(path)); + // Write stuff to the output file + if (content) { fh << content; } + if (footer) { fh << footer; } + // Close file-handle + fh.close(); + // Check for error state after closing + // This should report also write errors + if (!fh) throw Exception::IoError(compiler, + "Error writing output file", + File::abs2rel(path)); + } + else { + // Simply print results to stdout + if (content) std::cout << content; + if (footer) std::cout << footer; + } + } + + } + // Otherwise write special error css + else if (path && compiler.hasOutputFile()) { + std::ofstream fh(path, std::ios::out | std::ios::binary); + // Skip writing if we already had an error + if (compiler.error.status && !fh) return; + if (!fh) throw Exception::IoError(compiler, + "Error opening output file", + File::abs2rel(path)); + // Write stuff to the output file + compiler.error.writeCss(fh); + // Close file-handle + fh.close(); + // Check for error state after closing + // This should report also write errors + if (!fh) throw Exception::IoError(compiler, + "Error writing output file", + File::abs2rel(path)); + } + + } + // EO _sass_compiler_write_output + + // Main implementation (caller is wrapped in try/catch) + void _sass_compiler_write_srcmap(Compiler& compiler) + { + // Write to srcmap only if no errors occurred + if (compiler.error.status != 0) return; + + const char* srcmap = compiler.srcmap; + const char* path = compiler.mapopt.path.empty() ? + nullptr : compiler.mapopt.path.c_str(); + + // Write source-map if needed + if (srcmap && path && compiler.hasSrcMapFile()) { + std::ofstream fh(path, std::ios::out | std::ios::binary); + if (!fh) throw std::runtime_error("Error opening srcmap file"); + // Write stuff to the srcmap file + if (srcmap) { fh << srcmap; } + // Close file-handle + fh.close(); + } + + } + + void _sass_compiler_add_custom_function(Compiler& compiler, struct SassFunction* function) + { + compiler.registerCustomFunction(function); + } + // EO _sass_compiler_add_custom_function + + ///////////////////////////////////////////////////////////////////////// + // On windows we can improve the error handling by also catching + // structured exceptions. In order for this to work we need a few + // additional wrapper functions, which fortunately don't cost much. + ///////////////////////////////////////////////////////////////////////// + + #ifdef _MSC_VER + // Helper function to filter how to handle exceptions + int filter(unsigned int code, struct _EXCEPTION_POINTERS* ep) + { + // Handle exceptions we can't handle otherwise + // Because these are not regular C++ exceptions + if (code == EXCEPTION_ACCESS_VIOLATION) + { + return EXCEPTION_EXECUTE_HANDLER; + } + else if (code == EXCEPTION_STACK_OVERFLOW) + { + return EXCEPTION_EXECUTE_HANDLER; + } + else + { + // Do not handle any other exceptions here + // Should be handled by regular try/catch + return EXCEPTION_CONTINUE_SEARCH; + } + } + + // Convert exception codes to strings + const char* seException(unsigned int code) + { + switch (code) { + case EXCEPTION_ACCESS_VIOLATION: return "EXCEPTION_ACCESS_VIOLATION"; + case EXCEPTION_ARRAY_BOUNDS_EXCEEDED: return "EXCEPTION_ARRAY_BOUNDS_EXCEEDED"; + case EXCEPTION_BREAKPOINT: return "EXCEPTION_BREAKPOINT"; + case EXCEPTION_DATATYPE_MISALIGNMENT: return "EXCEPTION_DATATYPE_MISALIGNMENT"; + case EXCEPTION_FLT_DENORMAL_OPERAND: return "EXCEPTION_FLT_DENORMAL_OPERAND"; + case EXCEPTION_FLT_DIVIDE_BY_ZERO: return "EXCEPTION_FLT_DIVIDE_BY_ZERO"; + case EXCEPTION_FLT_INEXACT_RESULT: return "EXCEPTION_FLT_INEXACT_RESULT"; + case EXCEPTION_FLT_INVALID_OPERATION: return "EXCEPTION_FLT_INVALID_OPERATION"; + case EXCEPTION_FLT_OVERFLOW: return "EXCEPTION_FLT_OVERFLOW"; + case EXCEPTION_FLT_STACK_CHECK: return "EXCEPTION_FLT_STACK_CHECK"; + case EXCEPTION_FLT_UNDERFLOW: return "EXCEPTION_FLT_UNDERFLOW"; + case EXCEPTION_ILLEGAL_INSTRUCTION: return "EXCEPTION_ILLEGAL_INSTRUCTION"; + case EXCEPTION_IN_PAGE_ERROR: return "EXCEPTION_IN_PAGE_ERROR"; + case EXCEPTION_INT_DIVIDE_BY_ZERO: return "EXCEPTION_INT_DIVIDE_BY_ZERO"; + case EXCEPTION_INT_OVERFLOW: return "EXCEPTION_INT_OVERFLOW"; + case EXCEPTION_INVALID_DISPOSITION: return "EXCEPTION_INVALID_DISPOSITION"; + case EXCEPTION_NONCONTINUABLE_EXCEPTION: return "EXCEPTION_NONCONTINUABLE_EXCEPTION"; + case EXCEPTION_PRIV_INSTRUCTION: return "EXCEPTION_PRIV_INSTRUCTION"; + case EXCEPTION_SINGLE_STEP: return "EXCEPTION_SINGLE_STEP"; + case EXCEPTION_STACK_OVERFLOW: return "EXCEPTION_STACK_OVERFLOW"; + default: return "UNKNOWN EXCEPTION"; + } + } + #endif + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // Wrap Structured Exceptions for MSVC (void on no MSVC compilers) + template void sass_wrap_msvc_exception( + T& compiler, void (*fn)(T& compiler, ARGS...), ARGS... args) + { + #ifdef _MSC_VER + __try { + #endif + fn(compiler, args...); + #ifdef _MSC_VER + } + __except (filter(GetExceptionCode(), GetExceptionInformation())) { + throw std::runtime_error(seException(GetExceptionCode())); + } + #endif + } + // EO sass_wrap_msvc_exception + + // Wrap C++ exceptions and add to logger if any occur + template void sass_wrap_exception( + T& compiler, void (*fn)(T& compiler, ARGS...), ARGS... args) + { + Logger& logger(compiler); + try { sass_wrap_msvc_exception(compiler, fn, args...); } + catch (...) { handle_errors(compiler); } + compiler.warnings = logger.logstrm.str(); + } + // EO sass_wrap_exception + +} +// EO C++ helpers + + +///////////////////////////////////////////////////////////////////////// +// The actual C-API Implementations +///////////////////////////////////////////////////////////////////////// + +extern "C" { + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + using namespace Sass; + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // Create a new LibSass compiler context + struct SassCompiler* ADDCALL sass_make_compiler() + { + return Compiler::wrap(new Compiler()); + } + + // Release all memory allocated with the compiler + void ADDCALL sass_delete_compiler(struct SassCompiler* compiler) + { + delete& Compiler::unwrap(compiler); + #ifdef DEBUG_SHARED_PTR + RefCounted::dumpMemLeaks(); + #endif + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // Parse the entry point and potentially all imports within. + void ADDCALL sass_compiler_parse(struct SassCompiler* compiler) + { + sass_wrap_exception(Compiler::unwrap(compiler), _sass_compiler_parse); + } + + // Evaluate the parsed entry point and store resulting ast-tree. + void ADDCALL sass_compiler_compile(struct SassCompiler* compiler) + { + sass_wrap_exception(Compiler::unwrap(compiler), _sass_compiler_compile); + } + + // Render the evaluated ast-tree to get the final output string. + void ADDCALL sass_compiler_render(struct SassCompiler* compiler) + { + sass_wrap_exception(Compiler::unwrap(compiler), _sass_compiler_render); + } + + // Write or print the output to the console or the configured output path + void ADDCALL sass_compiler_write_output(struct SassCompiler* compiler) + { + sass_wrap_exception(Compiler::unwrap(compiler), _sass_compiler_write_output); + } + + // Write source-map to configured path if options are set accordingly + void ADDCALL sass_compiler_write_srcmap(struct SassCompiler* compiler) + { + sass_wrap_exception(Compiler::unwrap(compiler), _sass_compiler_write_srcmap); + } + + // Execute all compiler steps and write/print results + int ADDCALL sass_compiler_execute(struct SassCompiler* compiler) + { + + // Execute all compiler phases + // Will skip steps if one errors + sass_compiler_parse(compiler); + sass_compiler_compile(compiler); + sass_compiler_render(compiler); + + // First print all warnings and deprecation messages + if (!sass_compiler_get_suppress_stderr(compiler)) { + if (sass_compiler_get_warn_string(compiler)) { + sass_print_stderr(sass_compiler_get_warn_string(compiler)); + } + } + + // Get original compiler exit status to return + int result = sass_compiler_get_status(compiler); + + // Write/print the results + sass_compiler_write_output(compiler); + sass_compiler_write_srcmap(compiler); + + // Check for errors + if (result != 0) { + // Print error message if we have an error + const struct SassError* error = sass_compiler_get_error(compiler); + if (error) sass_print_stderr(sass_error_get_formatted(error)); + } + + // Return exit status + return result; + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // Add additional include paths where LibSass will look for includes. + // Note: the passed in `paths` can be path separated (`;` on windows, `:` otherwise). + void ADDCALL sass_compiler_add_include_paths(struct SassCompiler* compiler, const char* paths) + { + Compiler::unwrap(compiler).addIncludePaths(paths); + } + + // Load dynamic loadable plugins from `paths`. Plugins are only supported on certain OSs and + // are still in experimental state. This will look for `*.dll`, `*.so` or `*.dynlib` files. + // It then tries to load the found libraries and does a few checks to see if the library + // is actually a LibSass plugin. We then call its init hook if the library is compatible. + // Note: the passed in `paths` can be path separated (`;` on windows, `:` otherwise). + void ADDCALL sass_compiler_load_plugins(struct SassCompiler* compiler, const char* paths) + { + Compiler::unwrap(compiler).loadPlugins(paths); + } + + // Add a custom header importer that will always be executed before any other + // compilations takes place. Useful to prepend a shared copyright header or to + // provide global variables or functions. This feature is still in experimental state. + // Note: With the adaption of Sass Modules this might be completely replaced in the future. + void ADDCALL sass_compiler_add_custom_header(struct SassCompiler* compiler, struct SassImporter* header) + { + Compiler::unwrap(compiler).addCustomHeader(header); + } + + // Add a custom importer that will be executed when a sass `@import` rule is found. + // This is useful to e.g. rewrite import locations or to load content from remote. + // For more please check https://github.com/sass/libsass/blob/master/docs/api-importer.md + // Note: The importer will not be called for regular css `@import url()` rules. + void ADDCALL sass_compiler_add_custom_importer(struct SassCompiler* compiler, struct SassImporter* importer) + { + Compiler::unwrap(compiler).addCustomImporter(importer); + } + + // Add a custom function that will be executed when the corresponding function call is + // requested from any sass code. This is useful to provide custom functions in your code. + // For more please check https://github.com/sass/libsass/blob/master/docs/api-function.md + // Note: since we need to parse the function signature this may throw an error! + void ADDCALL sass_compiler_add_custom_function(struct SassCompiler* compiler, struct SassFunction* function) + { + // Wrap to correctly trap errors and to fill the error object if needed + sass_wrap_exception(Compiler::unwrap(compiler), _sass_compiler_add_custom_function, function); + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // Setter for output style (see `enum SassOutputStyle` for possible options). + void ADDCALL sass_compiler_set_input_syntax(struct SassCompiler* compiler, enum SassImportSyntax syntax) + { + Compiler::unwrap(compiler).input_syntax = syntax; + } + + // Setter for output style (see `enum SassOutputStyle` for possible options). + void ADDCALL sass_compiler_set_output_style(struct SassCompiler* compiler, enum SassOutputStyle style) + { + Compiler::unwrap(compiler).output_style = style; + } + + // Try to detect and set logger options for terminal colors, unicode and columns. + void ADDCALL sass_compiler_autodetect_logger_capabilities(struct SassCompiler* compiler) + { + Compiler::unwrap(compiler).autodetectCapabalities(); + } + + // Setter for enabling/disabling logging with ANSI colors. + void ADDCALL sass_compiler_set_logger_colors(struct SassCompiler* compiler, bool enable) + { + Compiler::unwrap(compiler).support_colors = enable; + } + + // Setter for enabling/disabling logging with unicode text. + void ADDCALL sass_compiler_set_logger_unicode(struct SassCompiler* compiler, bool enable) + { + Compiler::unwrap(compiler).support_unicode = enable; + } + + // Getter for number precision (how floating point numbers are truncated). + int ADDCALL sass_compiler_get_precision(struct SassCompiler* compiler) + { + return Compiler::unwrap(compiler).precision; + } + + // Setter for number precision (how floating point numbers are truncated). + void ADDCALL sass_compiler_set_precision(struct SassCompiler* compiler, int precision) + { + Compiler::unwrap(compiler).setPrecision(precision); + } + + // Getter for compiler entry point (which file or data to parse first). + struct SassImport* ADDCALL sass_compiler_get_entry_point(struct SassCompiler* compiler) + { + return Compiler::unwrap(compiler).entry_point->wrap(); + } + + // Setter for compiler entry point (which file or data to parse first). + void ADDCALL sass_compiler_set_entry_point(struct SassCompiler* compiler, struct SassImport* import) + { + Compiler::unwrap(compiler).entry_point = &Import::unwrap(import); + } + + // Getter for compiler output path (where to store the result) + // Note: LibSass does not write the file, implementers should write to this path. + const char* ADDCALL sass_compiler_get_output_path(struct SassCompiler* compiler) + { + return Compiler::unwrap(compiler).output_path.c_str(); + } + + // Setter for compiler output path (where to store the result) + // Note: LibSass does not write the file, implementers should write to this path. + void ADDCALL sass_compiler_set_output_path(struct SassCompiler* compiler, const char* output_path) + { + Compiler::unwrap(compiler).output_path = output_path ? output_path : "stream://stdout"; + } + + // Getter for option to suppress anything being printed on stderr (quiet mode) + bool ADDCALL sass_compiler_get_suppress_stderr(struct SassCompiler* compiler) + { + return Compiler::unwrap(compiler).suppress_stderr; + } + + // Setter for option to suppress anything being printed on stderr (quiet mode) + void ADDCALL sass_compiler_set_suppress_stderr(struct SassCompiler* compiler, bool suppress) + { + Compiler::unwrap(compiler).suppress_stderr = suppress; + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // Getter for warnings that occurred during any step. + const char* ADDCALL sass_compiler_get_warn_string(struct SassCompiler* compiler) + { + if (Compiler::unwrap(compiler).warnings.empty()) return nullptr; + Compiler::unwrap(compiler).reportSuppressedWarnings(); + return Compiler::unwrap(compiler).warnings.c_str(); + } + + // Getter for output after parsing, compilation and rendering. + const char* ADDCALL sass_compiler_get_output_string(struct SassCompiler* compiler) + { + if (Compiler::unwrap(compiler).content.empty()) return nullptr; + return Compiler::unwrap(compiler).content.c_str(); + } + + // Getter for footer string containing optional source-map (embedded or link). + const char* ADDCALL sass_compiler_get_footer_string(struct SassCompiler* compiler) + { + return Compiler::unwrap(compiler).footer; + } + + // Getter for string containing the optional source-mapping. + const char* ADDCALL sass_compiler_get_srcmap_string(struct SassCompiler* compiler) + { + return Compiler::unwrap(compiler).srcmap; + } + + // Check if implementor is expected to write a output file + bool ADDCALL sass_compiler_has_output_file(struct SassCompiler* compiler) + { + return Compiler::unwrap(compiler).hasOutputFile(); + } + + // Check if implementor is expected to write a source-map file + bool ADDCALL sass_compiler_has_srcmap_file(struct SassCompiler* compiler) + { + return Compiler::unwrap(compiler).hasSrcMapFile(); + } + + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // Setter for source-map mode (how to embed or not embed the source-map). + void ADDCALL sass_compiler_set_srcmap_mode(struct SassCompiler* compiler, enum SassSrcMapMode mode) + { + Compiler::unwrap(compiler).mapopt.mode = mode; + } + + // Setter for source-map path (where to store the source-mapping). + // Note: if path is not explicitly given, we will deduct one from output path. + // Note: LibSass does not write the file, implementers should write to this path. + void ADDCALL sass_compiler_set_srcmap_path(struct SassCompiler* compiler, const char* path) + { + Compiler::unwrap(compiler).mapopt.path = path; + } + + // Getter for source-map path (where to store the source-mapping). + // Note: if path is not explicitly given, we will deduct one from output path. + // Note: the value will only be deducted after the main render phase is completed. + // Note: LibSass does not write the file, implementers should write to this path. + const char* ADDCALL sass_compiler_get_srcmap_path(struct SassCompiler* compiler) + { + const sass::string& path(Compiler::unwrap(compiler).mapopt.path); + return path.empty() ? nullptr : path.c_str(); + } + + // Setter for source-map root (simply passed to the resulting srcmap info). + // Note: if not given, no root attribute will be added to the srcmap info object. + void ADDCALL sass_compiler_set_srcmap_root(struct SassCompiler* compiler, const char* root) + { + Compiler::unwrap(compiler).mapopt.root = root; + } + + // Setter for source-map file-url option (renders urls in srcmap as `file://` urls) + void ADDCALL sass_compiler_set_srcmap_file_urls(struct SassCompiler* compiler, bool enable) + { + Compiler::unwrap(compiler).mapopt.file_urls = enable; + } + + // Setter for source-map embed-contents option (includes full sources in the srcmap info) + void ADDCALL sass_compiler_set_srcmap_embed_contents(struct SassCompiler* compiler, bool enable) + { + Compiler::unwrap(compiler).mapopt.embed_contents = enable; + } + + // Setter to enable more detailed source map (also meaning bigger payload). + // Mostly useful if you want to post process the results again where the more detailed + // source-maps might by used by downstream post-processor to point back to original files. + void ADDCALL sass_compiler_set_srcmap_details(struct SassCompiler* compiler, bool openers, bool closers) + { + Compiler::unwrap(compiler).mapopt.enable_closers = closers; + Compiler::unwrap(compiler).mapopt.enable_openers = openers; + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // Getter to return the number of all included files. + size_t ADDCALL sass_compiler_get_included_files_count(struct SassCompiler* compiler) + { + return Compiler::unwrap(compiler).included_sources.size(); + } + + // Getter to return path to the included file at position `n`. + const char* ADDCALL sass_compiler_get_included_file_path(struct SassCompiler* compiler, size_t n) + { + return Compiler::unwrap(compiler).included_sources.at(n)->getAbsPath(); + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // Getter for current import context. Use `SassImport` functions to query the state. + const struct SassImport* ADDCALL sass_compiler_get_last_import(struct SassCompiler* compiler) + { + return Compiler::unwrap(compiler).import_stack.back()->wrap(); + } + + // Returns status code for compiler (0 meaning success, anything else is an error) + int ADDCALL sass_compiler_get_status(struct SassCompiler* compiler) + { + return Compiler::unwrap(compiler).error.status; + } + + // Returns pointer to error object associated with compiler. + // Will be valid until the associated compiler is destroyed. + const struct SassError* ADDCALL sass_compiler_get_error(struct SassCompiler* compiler) + { + return &Compiler::unwrap(compiler).error; + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // Resolve a file relative to last import or include paths in the sass option struct. + char* ADDCALL sass_compiler_find_file(const char* file, struct SassCompiler* compiler) + { + sass::string path(Compiler::unwrap(compiler).findFile(file)); + return path.empty() ? nullptr : sass_copy_c_string(path.c_str()); + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + //size_t ADDCALL sass_compiler_count_traces(struct SassCompiler* compiler) + //{ + // Logger& logger(Compiler::unwrap(compiler)); + // return logger.callStack.size(); + //} + // + //const struct SassTrace* ADDCALL sass_compiler_get_trace(struct SassCompiler* compiler, size_t i) + //{ + // Logger& logger(Compiler::unwrap(compiler)); + // return logger.callStack.at(i).wrap(); + //} + // + //const struct SassTrace* ADDCALL sass_compiler_last_trace(struct SassCompiler* compiler) + //{ + // Logger& logger(Compiler::unwrap(compiler)); + // return logger.callStack.back().wrap(); + //} + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // bool ADDCALL sass_compiler_get_source_comments(struct SassCompiler* compiler) + // { + // return Compiler::unwrap(compiler).source_comments; + // } + // + // void ADDCALL sass_compiler_set_source_comments(struct SassCompiler* compiler, bool source_comments) + // { + // Compiler::unwrap(compiler).source_comments = source_comments; + // } + +} diff --git a/src/capi_compiler.hpp b/src/capi_compiler.hpp new file mode 100644 index 0000000000..46ce79dbac --- /dev/null +++ b/src/capi_compiler.hpp @@ -0,0 +1,30 @@ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#ifndef SASS_CAPI_COMPILER_HPP +#define SASS_CAPI_COMPILER_HPP + +// sass.hpp must go before all system headers +// to get the __EXTENSIONS__ fix on Solaris. +#include "capi_sass.hpp" +#include "compiler.hpp" + +#include + +namespace Sass { + + // Promote and format error onto compiler with given status, message and traces + int handle_error(Compiler& compiler, int status, + const char* what = nullptr, StackTraces* traces = nullptr); + + // Wrap Structured Exceptions for MSVC (void on no MSVC compilers) + template void sass_wrap_msvc_exception( + T& compiler, void (*fn)(T& compiler, ARGS...), ARGS... args); + + // Wrap C++ exceptions and add to logger if any occur + template void sass_wrap_exception( + T& compiler, void (*fn)(T& compiler, ARGS...), ARGS... args); + +} + +#endif diff --git a/src/capi_error.cpp b/src/capi_error.cpp new file mode 100644 index 0000000000..8137dc3f6d --- /dev/null +++ b/src/capi_error.cpp @@ -0,0 +1,236 @@ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#include "capi_error.hpp" + +#include +#include "json.hpp" +#include "unicode.hpp" +#include "string_utils.hpp" + +using namespace Sass; + +// Create error formatted as serialized json. +// You must free the data returned from here. +// You must do so by using `sass_free_c_string` +char* SassError::getJson(bool include_sources) const +{ + + // Create json root object + JsonNode* json = json_mkobject(); + + // Attach all stack traces + if (traces.size() > 0) { + JsonNode* json_traces = json_mkarray(); + for (const StackTrace& trace : traces) { + JsonNode* json_trace = json_mkobject(); + const SourceSpan& pstate(trace.pstate); + json_append_member(json_trace, "file", json_mkstring(pstate.getAbsPath())); + json_append_member(json_trace, "line", json_mknumber((double)(pstate.getLine()))); + json_append_member(json_trace, "column", json_mknumber((double)(pstate.getColumn()))); + if (include_sources) json_append_member(json_trace, "source", json_mkstring(pstate.getContent())); + json_append_element(json_traces, json_trace); + } + json_append_member(json, "traces", json_traces); + } + + // Attach the generic error reporting items + json_append_member(json, "status", json_mknumber(status)); + json_append_member(json, "error", json_mkstring(what.c_str())); + json_append_member(json, "formatted", json_mkstring(formatted.c_str())); + + char* serialized = nullptr; + // Stringification may fail for strange reason + try { serialized = json_stringify(json, " "); } + // If it fails at least return a valid json with special status 9999 + catch (...) { serialized = sass_copy_c_string("{\"status\":9999}"); } + + // Delete json tree + json_delete(json); + + // Return new memory + return serialized; + +} +// EO SassError::getJson + +// Write error style-sheet so errors are shown +// in the browser if the stylesheet is loaded. +void SassError::writeCss(std::ostream& css) const +{ + // Create a copy to replace chars + sass::string comment(formatted); + StringUtils::makeRightTrimmed(comment); + + // Remove ANSI color codes + size_t found = comment.find_first_of("\x1B"); + while (found != std::string::npos) { + auto end = found + 2; + while (comment[end] != '\0' && comment[end] != ';' && comment[end] != 'm') end += 1; + comment.replace(found, end - found + (comment[end] == '\0' ? 0 : 1), ""); + found = comment.find_first_of("\x1B", found); + } + + // Create copy for content escape + sass::string content(comment); + + // Sanitize comment closer with unicode + found = comment.find_first_of("/"); + while (found != std::string::npos) { + if (found != 0 && comment[found - 1] == '*') { + comment.replace(found, 2, "*\xE2\x88\x95"); + } + found = comment.find_first_of("/", found + 4); + } + // Prefix each line with another star + found = comment.find_first_of("\n"); + while (found != std::string::npos) { + comment.replace(found, 1, "\n * "); + found = comment.find_first_of("\n", found + 1); + } + // Add a css comment with the error + css << "/* " << comment << "\n */\n"; + + // Escape all existing backslashes + found = content.find_first_of("\\"); + while (found != std::string::npos) { + content.replace(found, 1, "\\\\"); + found = content.find_first_of("\\", found + 2); + } + // Escape all quotes to paste into content + found = content.find_first_of("\""); + while (found != std::string::npos) { + content.replace(found, 1, "\\\""); + found = content.find_first_of("\"", found + 2); + } + // Escape newlines with css escapes + found = content.find_first_of("\n"); + while (found != std::string::npos) { + content.replace(found, 1, "\\a "); + found = content.find_first_of("\n", found + 2); + } + + // Create body style-rule to show error in UA + css << "body::before{\n"; + css << " font-family: \"Source Code Pro\", \"SF Mono\", Monaco, Inconsolata, \"Fira Mono\",\n"; + css << " \"Droid Sans Mono\", monospace, monospace;\n"; + css << " white-space: pre;\n"; + css << " display: block; \n"; + css << " padding: 1em; \n"; + css << " margin-bottom: 1em; \n"; + css << " border-bottom: 2px solid black; \n"; + css << " content: \""; + + // Print the escaped content now, code-point by code-point + auto cur = content.begin(); + auto end = content.end(); + while (cur != end) { + uint32_t cp = utf8::next(cur, end); + if (cp <= 127) { + // Just pass the char + css << (char)cp; + } + else { + // Write css unicode escape sequence + // Must be followed by trailing space! + css << "\\" << std::hex << cp << " "; + } + } + css << "\"\n" << "}\n"; +} +// EO SassError::writeCss + +// Getter for error status as css +// In order to show error in browser. +char* SassError::getCss() const +{ + sass::ostream css; + writeCss(css); + return sass_copy_string(css.str()); +} +// EO SassError::getCss + +#ifdef __cplusplus +extern "C" { +#endif + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + using namespace Sass; + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // Error related getters (use after compiler was rendered) + int ADDCALL sass_error_get_status(const struct SassError* error) { return error->status; } + + // Getter for plain error message (use after compiler was rendered). + const char* ADDCALL sass_error_get_string(const struct SassError* error) { return error->what.c_str(); } + + // Getter for error status as css (In order to show error in browser). + // Memory returned by this function must be freed via `sass_free_c_string`. + char* ADDCALL sass_error_get_css(const struct SassError* error) { return error->getCss(); } + + // Getter for error status as json object (Useful to pass to downstream). + // Memory returned by this function must be freed via `sass_free_c_string`. + char* ADDCALL sass_error_get_json(const struct SassError* error) { return error->getJson(true); } + + // Getter for formatted error message. According to logger style this + // may be in unicode and may contain ANSI escape codes for colors. + const char* ADDCALL sass_error_get_formatted(const struct SassError* error) { return error->formatted.c_str(); } + + // Getter for line position where error occurred (starts from 1). + size_t ADDCALL sass_error_get_line(const struct SassError* error) + { + if (error->traces.empty()) return 0; + return error->traces.back().pstate.getLine(); + } + + // Getter for column position where error occurred (starts from 1). + size_t ADDCALL sass_error_get_column(const struct SassError* error) + { + if (error->traces.empty()) return 0; + return error->traces.back().pstate.getColumn(); + } + + // Getter for source content referenced in line and column. + const char* ADDCALL sass_error_get_content(const struct SassError* error) + { + if (error->traces.empty()) return 0; + return error->traces.back().pstate.getContent(); + } + + // Getter for path where the error occurred. + const char* ADDCALL sass_error_get_path(const struct SassError* error) + { + if (error->traces.empty()) return nullptr; + return error->traces.back().pstate.getAbsPath(); + } + + // Getter for number of traces attached to error object. + size_t ADDCALL sass_error_count_traces(const struct SassError* error) + { + return error->traces.size(); + } + + // Getter for last trace (or nullptr if none are available). + const struct SassTrace* ADDCALL sass_error_last_trace(const struct SassError* error) + { + if (error->traces.empty()) return nullptr; + return error->traces.back().wrap(); + } + + // Getter for nth trace (or nullptr if `n` is invalid). + const struct SassTrace* ADDCALL sass_error_get_trace(const struct SassError* error, size_t i) + { + if (error->traces.size() < i) return nullptr; + return error->traces.at(i).wrap(); + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + +#ifdef __cplusplus +} // __cplusplus defined. +#endif diff --git a/src/capi_error.hpp b/src/capi_error.hpp new file mode 100644 index 0000000000..4e741e6272 --- /dev/null +++ b/src/capi_error.hpp @@ -0,0 +1,50 @@ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#ifndef SASS_CAPI_ERROR_HPP +#define SASS_CAPI_ERROR_HPP + +// sass.hpp must go before all system headers +// to get the __EXTENSIONS__ fix on Solaris. +#include "capi_sass.hpp" + +#include +#include "backtrace.hpp" + +// Struct for errors +struct SassError { + + // Error status + int status; + + // Error message + sass::string what; + + // Traces leading up to error + Sass::StackTraces traces; + + // Formatted error string, may contain + // unicode and/or ANSI color escape codes + sass::string formatted; + + // Constructor + SassError() : + status(0) + {} + + // Return json string to pass down-stream. + // You must free the returned data yourself. + // Do so by calling `sass_free_memory(ptr)`. + char* getJson(bool include_sources) const; + + // Getter for error status as css + // In order to show error in browser. + char* getCss() const; + + // Write error style-sheet so errors are shown + // in the browser if the stylesheet is loaded. + void writeCss(std::ostream& css) const; + +}; + +#endif diff --git a/src/capi_function.cpp b/src/capi_function.cpp new file mode 100644 index 0000000000..0dcf72e62f --- /dev/null +++ b/src/capi_function.cpp @@ -0,0 +1,60 @@ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#include "capi_function.hpp" + +extern "C" { + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + using namespace Sass; + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // Create custom function (with arbitrary data pointer called `cookie`) + // The pointer is often used to store the callback into the actual binding. + struct SassFunction* ADDCALL sass_make_function(const char* signature, SassFunctionLambda lambda, void* cookie) + { + if (lambda == nullptr) return nullptr; + if (signature == nullptr) return nullptr; + struct SassFunction* function = new SassFunction{}; + if (function == nullptr) return nullptr; + function->lambda = lambda; + function->signature = signature; + function->cookie = cookie; + return function; + } + + // Deallocate custom function and release memory + void ADDCALL sass_delete_function(struct SassFunction* function) + { + delete function; + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // Getter for custom function lambda. + SassFunctionLambda ADDCALL sass_function_get_lambda(struct SassFunction* function) + { + return function->lambda; + } + + // Getter for custom function signature. + const char* ADDCALL sass_function_get_signature(struct SassFunction* function) + { + return function->signature.c_str(); + } + + // Getter for custom function data cookie. + void* ADDCALL sass_function_get_cookie(struct SassFunction* function) + { + return function->cookie; + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + +} diff --git a/src/capi_function.hpp b/src/capi_function.hpp new file mode 100644 index 0000000000..c3da131a3e --- /dev/null +++ b/src/capi_function.hpp @@ -0,0 +1,27 @@ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#ifndef SASS_CAPI_FUNCTION_HPP +#define SASS_CAPI_FUNCTION_HPP + +// sass.hpp must go before all system headers +// to get the __EXTENSIONS__ fix on Solaris. +#include "capi_sass.hpp" + +#include + +// Struct for custom functions +struct SassFunction { + + // The C function to be invoked + SassFunctionLambda lambda; + + // Signature of function arguments + sass::string signature; + + // Arbitrary data cookie + void* cookie; + +}; + +#endif diff --git a/src/capi_getopt.cpp b/src/capi_getopt.cpp new file mode 100644 index 0000000000..10703d7b78 --- /dev/null +++ b/src/capi_getopt.cpp @@ -0,0 +1,685 @@ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#include "capi_getopt.hpp" + +#include +#include +#include +#include "exceptions.hpp" +#include "string_utils.hpp" +#include "capi_compiler.hpp" + +using namespace Sass; + +///////////////////////////////////////////////////////////////////////// +// This file implements something very similar to GNU (long) getopt. +// It supports parsing a list of string arguments to configure a compiler. +// Corresponds exactly to how SassC will parse `argv` command line +// arguments and makes this feature available to all implementors. +///////////////////////////////////////////////////////////////////////// +// So far we support boolean, string and enumeration options and we +// might extend this list if the need arises. Boolean options don't +// allow any argument, but can be inverted with the `--no-` prefix. +// Other options may have an additional argument and also a default. +///////////////////////////////////////////////////////////////////////// +// Some of the key features are: +// - Support for boolean options with [--no-] prefix +// - Short options don't support exclamation `!` mark yet +// - Supports name shortening, if target can be identified uniquely +///////////////////////////////////////////////////////////////////////// +// You may also use this API to completely use different or additional +// options. Although part of LibSass it could also be used standalone. +// APIs are quite raw and not optimized for multi purpose though. +///////////////////////////////////////////////////////////////////////// + + +// Single enumeration item for sass options +// Maps an option to the given enum integer. +struct SassGetOptEnum +{ +public: + int enumid; + const char* string; + SassGetOptEnum(const char* name, int id) : + enumid(id), + string(name) + {} +}; + +///////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////// + +class SassOption { +public: + const char shrt = '\0'; + const char* name; + const char* desc; + const bool boolean = false; + const char* argument = nullptr; + const bool optional = false; + const struct SassGetOptEnum* enums; + void (*cb) (struct SassGetOpt* getopt, union SassOptionValue value); + SassOption( + const char shrt = '\0', + const char* name = nullptr, + const char* desc = nullptr, + const bool boolean = false, + const char* argument = nullptr, + const bool optional = false, + const struct SassGetOptEnum* enums = nullptr, + void (*cb) (struct SassGetOpt* getopt, union SassOptionValue value) = nullptr + ) : + shrt(shrt), + name(name), + desc(desc), + boolean(boolean), + argument(argument), + optional(optional), + enums(enums), + cb(cb) + {} +}; + +///////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////// + +struct SassArgument { + bool optional = false; + const char* name; + void (*cb) (struct SassGetOpt* getopt, const char* arg); + SassArgument( + bool optional = false, + const char* name = nullptr, + void (*cb) (struct SassGetOpt* getopt, const char* arg) = nullptr + ) : + optional(optional), + name(name), + cb(cb) + {} +}; + +///////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////// + +struct SassGetOpt { + Compiler& compiler; + sass::string wasAssignment; + bool lastArgWasShort = false; + bool needsArgumentWasShort = false; + const SassOption* lastArg = nullptr; + const SassOption* needsArgument = nullptr; + sass::vector args = {}; + sass::vector options; + sass::vector arguments; + SassGetOpt(Compiler& compiler) : + compiler(compiler) + {} +}; + +///////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////// + +// Enums for input format option +const struct SassGetOptEnum format_options[] = { + { "scss", SASS_IMPORT_SCSS }, + { "sass", SASS_IMPORT_SASS }, + { "css", SASS_IMPORT_CSS }, + { "auto", SASS_IMPORT_AUTO }, + { nullptr, 0 } +}; + +// Enums for output style option +const struct SassGetOptEnum style_options[] = { + { "nested", SASS_STYLE_NESTED }, + { "expanded", SASS_STYLE_EXPANDED }, + { "compact", SASS_STYLE_COMPACT }, + { "compressed", SASS_STYLE_COMPRESSED }, + { nullptr, 0 } +}; + +// Enums for source-map mode option +const struct SassGetOptEnum srcmap_options[] = { + { "none", SASS_SRCMAP_NONE }, + { "create", SASS_SRCMAP_CREATE }, + { "link", SASS_SRCMAP_EMBED_LINK }, + { "embed", SASS_SRCMAP_EMBED_JSON }, + { nullptr, 0 } +}; + +// Simple proxy functions to call out to compiler (or set certain options directly) +void getopt_set_input_format(struct SassGetOpt* getopt, union SassOptionValue value) { getopt->compiler.input_syntax = value.syntax; } +void getopt_set_output_style(struct SassGetOpt* getopt, union SassOptionValue value) { getopt->compiler.output_style = value.style; } +void getopt_add_include_path(struct SassGetOpt* getopt, union SassOptionValue value) { getopt->compiler.addIncludePaths(value.string); } +void getopt_load_plugins(struct SassGetOpt* getopt, union SassOptionValue value) { getopt->compiler.loadPlugins(value.string); } +void getopt_set_srcmap_mode(struct SassGetOpt* getopt, union SassOptionValue value) { getopt->compiler.mapopt.mode = value.mode; } +void getopt_set_srcmap_file_urls(struct SassGetOpt* getopt, union SassOptionValue value) { getopt->compiler.mapopt.file_urls = value.boolean; } +void getopt_set_srcmap_contents(struct SassGetOpt* getopt, union SassOptionValue value) { getopt->compiler.mapopt.embed_contents = value.boolean; } +void getopt_set_srcmap_root(struct SassGetOpt* getopt, union SassOptionValue value) { getopt->compiler.mapopt.root = value.boolean; } +void getopt_set_srcmap_path(struct SassGetOpt* getopt, union SassOptionValue value) { getopt->compiler.mapopt.path = value.boolean; } +void getopt_set_term_unicode(struct SassGetOpt* getopt, union SassOptionValue value) { getopt->compiler.support_unicode = value.boolean; } +void getopt_set_term_colors(struct SassGetOpt* getopt, union SassOptionValue value) { getopt->compiler.support_colors = value.boolean; } +void getopt_set_suppress_stderr(struct SassGetOpt* getopt, union SassOptionValue value) { getopt->compiler.suppress_stderr = true; } + +void getopt_error(struct SassGetOpt* getopt, const char* what) +{ + if (getopt) { + handle_error(getopt->compiler, 9, what, nullptr); + } +} + +// Precision setter has specific validation (no corresponding type in GetOpt parser) +void getopt_set_precision(struct SassGetOpt* getopt, union SassOptionValue value) +{ + // The GetOpt API does not yet know integers + try { + // Try to convert the precision string to int + getopt->compiler.setPrecision(std::stoi(value.string)); + } + catch (std::exception&) { + getopt_error(getopt, "option '--precision' is not a valid integer"); + return; // return after error + } +} + +void cli_sass_compiler_set_line_numbers(struct SassGetOpt* getopt, union SassOptionValue value) { + std::cerr << "cli_sass_compiler_set_line_numbers " << value.boolean << "\n"; +} + + +void getopt_print_help(struct SassGetOpt* getopt, std::ostream& stream); + +void cli_sass_compiler_version(struct SassGetOpt* getopt, union SassOptionValue value) { + getopt_print_help(getopt, std::cerr); + exit(0); +} + +void cli_sass_compiler_help(struct SassGetOpt* getopt, union SassOptionValue value) { + getopt_print_help(getopt, std::cerr); + exit(0); +} + +void cli_sass_compiler_input_file_arg(struct SassGetOpt* getopt, const char* path) +{ + struct SassImport* entry = strncmp(path, "--", 2) == 0 + ? sass_make_stdin_import("stream://stdin") + : sass_make_file_import(path); + sass_compiler_set_entry_point(getopt->compiler.wrap(), entry); + sass_delete_import(entry); +} + +void cli_sass_compiler_output_file_arg(struct SassGetOpt* getopt, const char* path) +{ + sass_compiler_set_output_path(getopt->compiler.wrap(), path); +} + + +sass::string format_option(struct SassGetOpt* getopt, SassOption& option) +{ + Compiler& compiler(getopt->compiler); + sass::sstream line; + if (option.shrt) { + line << compiler.getTerm(Terminal::bold_magenta); + line << "-" << option.shrt; + line << compiler.getTerm(Terminal::reset); + if (option.name) line << ", "; + } + else { + line << " "; + } + if (option.name) { + line << compiler.getTerm(Terminal::green); + line << "--"; + line << compiler.getTerm(Terminal::reset); + if (option.boolean) { + line << compiler.getTerm(Terminal::blue); + line << "[no-]"; + line << compiler.getTerm(Terminal::reset); + } + line << compiler.getTerm(Terminal::green); + line << option.name; + line << compiler.getTerm(Terminal::reset); + } + if (option.argument) { + if (option.optional) line << "["; + line << "="; + line << compiler.getTerm(Terminal::cyan); + line << option.argument; + line << compiler.getTerm(Terminal::reset); + if (option.optional) line << "]"; + } + return line.str(); +} + +void getopt_print_help(struct SassGetOpt* getopt, std::ostream& stream) +{ + size_t longest = 20; + // Determine longest option to align them all + for (SassOption& option : getopt->options) { + sass::string fmt(format_option(getopt, option)); + size_t len = Terminal::count_printable(fmt.c_str()) + 2; + if (len > longest) longest = len; + } + // Print out each option line by line + for (SassOption& option : getopt->options) { + sass::string fmt(format_option(getopt, option)); + size_t len = Terminal::count_printable(fmt.c_str()); + stream << " " << fmt; + stream.width(longest - len + 2); + stream << " " << option.desc; + stream << "\n"; + if (option.enums) { + auto enums = option.enums; + sass::vector names; + while (enums && enums->string) { + names.push_back(enums->string); + enums += 1; + } + stream << std::setw(longest + 4) << ""; + if (option.argument) { + stream << getopt->compiler.getTerm(Terminal::cyan); + stream << option.argument; + stream << getopt->compiler.getTerm(Terminal::reset); + stream << " must be "; + } + stream << toSentence(names, "or", + getopt->compiler.getTerm(Terminal::yellow), + getopt->compiler.getTerm(Terminal::reset), + '\'') << "\n"; + } + } +} + +sass::vector find_short_options(struct SassGetOpt* getopt, const char arg) +{ + sass::vector matches; + for (SassOption& option : getopt->options) { + if (option.shrt == arg) { + matches.push_back(&option); + } + } + return matches; +} + +sass::vector find_long_options(struct SassGetOpt* getopt, const sass::string& arg) +{ + sass::vector matches; + for (const SassOption& option : getopt->options) { + if (StringUtils::startsWithIgnoreCase(option.name, arg)) { + if (arg == option.name) return { &option }; + matches.push_back(&option); + } + if (option.boolean) { + if (StringUtils::startsWithIgnoreCase(arg, "no-", 3)) { + sass::string name(arg.begin() + 3, arg.end()); + if (StringUtils::startsWithIgnoreCase(option.name, name)) { + if (arg == option.name) return { &option }; + matches.push_back(&option); + } + } + } + } + return matches; +} + +sass::vector find_options_enum( + const struct SassGetOptEnum* enums, const sass::string& arg) +{ + sass::vector matches; + while (enums && enums->string) { + if (StringUtils::startsWithIgnoreCase(enums->string, arg)) { + matches.push_back(enums); + } + enums += 1; + } + return matches; +} + +// Check for too many or not enough arguments +// Skip this check if nothing is expected at all +// This will simply store the arguments in `args` +void getopt_check_and_consume_arguments(struct SassGetOpt* getopt) +{ + if (getopt->compiler.state) return; + if (getopt->arguments.empty()) return; + size_t want_size = getopt->arguments.size(); + size_t requires = getopt->arguments.size(); + for (const auto& arg : getopt->arguments) { + if (arg.optional) requires -= 1; + } + size_t have_size = getopt->args.size(); + for (size_t i = 0; i < have_size; i += 1) { + if (want_size <= i) { + sass::sstream strm; + sass::string value(getopt->args[i]); + StringUtils::makeReplace(value, "'", "\\'"); + strm << "extra argument '" << value << "'"; + sass::string msg(strm.str()); + getopt_error(getopt, msg.c_str()); + return; // return after error + } + else { + // Call back to registered handler + getopt->arguments[i].cb(getopt, getopt->args[i].c_str()); + } + } + for (size_t i = have_size; i < requires; i += 1) { + sass::sstream strm; + sass::string value(getopt->arguments[i].name); + StringUtils::makeReplace(value, "'", "\\'"); + strm << "missing required argument '" << value << "'"; + sass::string msg(strm.str()); + getopt_error(getopt, msg.c_str()); + return; // return after error + } + +} + +// Check for pending required option +void getopt_check_required_option(struct SassGetOpt* getopt) +{ + if (getopt->compiler.state) return; + // Expected argument, error + if (getopt->needsArgument) { + sass::sstream strm; + if (getopt->needsArgumentWasShort) { + sass::string value(1, getopt->needsArgument->shrt); + // StringUtils::makeReplace(value, "'", "\\'"); // only a char + strm << "option '-" << value << "' requires an argument'"; + } + else { + sass::string value(getopt->needsArgument->name); + StringUtils::makeReplace(value, "'", "\\'"); + strm << "option '--" << value << "' requires an argument'"; + } + sass::string msg(strm.str()); + getopt_error(getopt, msg.c_str()); + return; // return after error + } +} + +// Function that must be consecutively called for every argument. +// Ensures to properly handle cases where a mandatory or optional +// argument, if required by the previous option, is handled correctly. +// This is a bit different to "official" GNU GetOpt, but should be +// reasonably well and support more advanced usages than before. +void getopt_parse(struct SassGetOpt* getopt, const char* value) +{ + if (value == nullptr) return; + if (getopt->compiler.state) return; + sass::string arg(value); + StringUtils::makeTrimmed(arg); + union SassOptionValue result {}; + + if (arg != "-" && arg != "--" && + arg[0] == '-' && getopt->wasAssignment.empty()) + { + getopt_check_required_option(getopt); + sass::vector opts; + + // Check if we have some assignment + size_t assign = arg.find_first_of('='); + if (assign != std::string::npos) { + sass::string key(arg.begin(), arg.begin() + assign); + sass::string val(arg.begin() + assign + 1, arg.end()); + getopt_parse(getopt, key.c_str()); + getopt->wasAssignment = key; + getopt_parse(getopt, val.c_str()); + getopt->wasAssignment.clear(); + return; + } + + // Long argument + if (arg[1] == '-') { + arg.erase(0, 2); + opts = find_long_options(getopt, arg); + getopt_check_required_option(getopt); + } + // Short argument + else { + arg.erase(0, 1); + // Split multiple short args + if (arg.size() > 1) { + for (size_t i = 0; i < arg.size(); i += 1) { + sass::string split("-"); split += arg[i]; + sass_getopt_parse(getopt, split.c_str()); + getopt_check_required_option(getopt); + // break on first error + } + return; + } + opts = find_short_options(getopt, arg[0]); + // Consume further until has arg + } + if (opts.size() == 0) { + sass::sstream strm; + strm << "unrecognized option '--" << arg << "'"; + sass::string msg(strm.str()); + getopt_error(getopt, msg.c_str()); + return; // return after error + } + if (opts.size() > 1) { + sass::sstream strm; + if (value[0] == '-' && value[1] == '-') { + strm << "option '--" << arg << "' is ambiguous; possibilities: "; + for (auto opt : opts) strm << "'--" << opt->name << "'" << std::setw(4); + } + else { + // Should never happen if you configured your options right + // Triggered by sassc.exe -MP + strm << "option '-" << arg << "' is ambiguous1 (internal error)"; + for (auto opt : opts) strm << "'--" << opt->name << "'" << std::setw(4); + } + sass::string msg(strm.str()); + getopt_error(getopt, msg.c_str()); + return; // return after error + } + getopt->lastArg = opts[0]; + getopt->needsArgument = opts[0]->argument ? opts[0] : nullptr; + getopt->needsArgumentWasShort = value[0] == '-' && value[1] != '-'; + + // Check boolean options right away + if (opts[0]->boolean) { + // Get boolean result (invert if argument has "no-" prefix) + result.boolean = !StringUtils::startsWithIgnoreCase(arg, "no-", 3); + } + if (!getopt->needsArgument) { + opts[0]->cb(getopt, result); + } + } + else if (getopt->needsArgument) { + if (getopt->needsArgument->enums) { + auto matches = find_options_enum( + getopt->needsArgument->enums, arg); + if (matches.size() == 0) { + sass::sstream strm; + strm << "enum '" << arg << "' is not valid for option '"; + if (getopt->needsArgumentWasShort) { + strm << "-" << getopt->needsArgument->shrt; + } + else { + strm << "--" << getopt->needsArgument->name; + } + strm << "' (valid enums are "; + auto enums = getopt->needsArgument->enums; + sass::vector names; + while (enums && enums->string) { + names.push_back(enums->string); + enums += 1; + } + strm << Sass::toSentence(names, "or", + getopt->compiler.getTerm(Terminal::yellow), + getopt->compiler.getTerm(Terminal::reset), '\'') << ")"; + sass::string msg(strm.str()); + getopt_error(getopt, msg.c_str()); + return; // return after error + } + else if (matches.size() > 1) { + + sass::sstream strm; + strm << "enum '" << arg << "' for option '"; + if (getopt->needsArgumentWasShort) { + strm << "-" << getopt->needsArgument->shrt; + } + else { + strm << "--" << getopt->needsArgument->name; + } + strm << "' is ambiguous (possibilities are "; + sass::vector names; + for (auto match : matches) { + names.push_back(match->string); + } + strm << toSentence(names, "or", + getopt->compiler.getTerm(Terminal::yellow), + getopt->compiler.getTerm(Terminal::reset), '\'') << ")"; + sass::string msg(strm.str()); + getopt_error(getopt, msg.c_str()); + return; // return after error + } + result.integer = matches[0]->enumid; + } + else { + result.string = arg.c_str(); + } + getopt->needsArgument->cb(getopt, result); + getopt->needsArgumentWasShort = false; + getopt->needsArgument = nullptr; + } + else if (!getopt->wasAssignment.empty()) { + sass::sstream strm; + strm << "option '"; + if (getopt->lastArgWasShort) { + strm << "-" << getopt->lastArg->shrt; + } + else { + strm << "--" << getopt->lastArg->name; + } + strm << "' doesn't allow an argument"; + sass::string msg(strm.str()); + getopt_error(getopt, msg.c_str()); + return; // return after error + } + else { + // This is a regular argument + getopt->args.push_back(arg); + } + +} + +///////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////// + +extern "C" { + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // Create a new GetOpt parser to help with parsing config from users + // Optimized to act like GNU GetOpt Long to consume `argv` items + // But can also be used to parse any other list of config strings + struct SassGetOpt* ADDCALL sass_make_getopt(struct SassCompiler* compiler) + { + return new struct SassGetOpt(Compiler::unwrap(compiler)); + } + // EO sass_make_getopt + + void ADDCALL sass_getopt_parse(struct SassGetOpt* getopt, const char* value) + { + getopt_parse(getopt, value); + } + + // Return string with the full help message describing all commands + // This is formatted in a similar fashion as GNU tools using getopt + char* ADDCALL sass_getopt_get_help(struct SassGetOpt* getopt) + { + sass::sstream usage; + getopt_print_help(getopt, usage); + return sass_copy_string(usage.str()); + } + // EO sass_getopt_get_help + + // Delete and finalize the GetOpt parser. Make sure to call + // this before you want to start the actual compilation phase. + void ADDCALL sass_delete_getopt(struct SassGetOpt* getopt) + { + // Check for pending required option + getopt_check_required_option(getopt); + // Check for too many or not enough arguments + // Skip this check if nothing is expected at all + // This will simply store the arguments in `args` + getopt_check_and_consume_arguments(getopt); + // Release memory + delete getopt; + } + // EO sass_delete_getopt + + // Register additional option that can be parsed + void ADDCALL sass_getopt_register_option(struct SassGetOpt* getopt, + // Short and long parameter names + const char short_name, + const char* long_name, + // Description used in help/usage message + const char* description, + // Whether to act like a boolean + const bool boolean, + // Name of required argument + const char* argument, + // Make argument optional + const bool optional, + // Arguments must be one of this enum + const struct SassGetOptEnum* enums, + // Callback function, where we pass back the given option value + void (*cb) (struct SassGetOpt* getopt, union SassOptionValue value)) + { + getopt->options.emplace_back(SassOption{ short_name, long_name, + description, boolean, argument, optional, enums, cb }); + } + // EO sass_getopt_register_option + + // Register additional argument that can be parsed + void ADDCALL sass_getopt_register_argument(struct SassGetOpt* getopt, + // Whether this argument is optional + bool optional, + // Name used in messages + const char* name, + // Callback function, where we pass back the given argument value + void (*cb) (struct SassGetOpt* getopt, const char* value)) + { + getopt->arguments.emplace_back(SassArgument{ optional, name, cb }); + } + // EO sass_getopt_register_argument + + // Utility function to tell LibSass to register its default options + void ADDCALL sass_getopt_populate_options(struct SassGetOpt* getopt) + { + + /* enum: style */ sass_getopt_register_option(getopt, 't', "style", "Set output style (nested, expanded, compact or compressed).", false, "STYLE", false, style_options, getopt_set_output_style); + /* enum: format */ sass_getopt_register_option(getopt, 'f', "format", "Set explicit input syntax (scss, sass, css or auto).", false, "SYNTAX", true, format_options, getopt_set_input_format); + /* path */ sass_getopt_register_option(getopt, 'I', "include-path", "Add include path to look for imports.", false, "PATH", false, nullptr, getopt_add_include_path); + /* path */ sass_getopt_register_option(getopt, 'P', "plugin-path", "Add plugin path to auto load plugins.", false, "PATH", false, nullptr, getopt_load_plugins); + /* enum: mode */ sass_getopt_register_option(getopt, 'm', "sourcemap", "Set how to create and emit source mappings.", false, "TYPE", true, srcmap_options, getopt_set_srcmap_mode); + /* bool */ sass_getopt_register_option(getopt, '\0', "sourcemap-file-urls", "Emit absolute file:// urls in includes array.", true, nullptr, true, nullptr, getopt_set_srcmap_file_urls); + /* bool */ sass_getopt_register_option(getopt, 'C', "sourcemap-contents", "Embed contents of imported files in source map.", true, nullptr, true, nullptr, getopt_set_srcmap_contents); + /* path */ sass_getopt_register_option(getopt, 'M', "sourcemap-path", "Set path where source map file is saved.", false, "PATH", false, nullptr, getopt_set_srcmap_path); + /* int */ sass_getopt_register_option(getopt, 'p', "precision", "Set floating-point precision for numbers.", false, "{0-12}", false, nullptr, getopt_set_precision); + /* bool */ // sass_getopt_register_option(getopt, '\0', "unicode", "Enable or disable unicode in generated css output.", true, nullptr, false, nullptr, getopt_set_unicode); + /* bool */ sass_getopt_register_option(getopt, 'l', "line-comments", "Emit comments showing original line numbers.", true, nullptr, false, nullptr, cli_sass_compiler_set_line_numbers); + /* bool */ sass_getopt_register_option(getopt, '\0', "term-unicode", "Enable or disable terminal unicode output.", true, nullptr, false, nullptr, getopt_set_term_unicode); + /* bool */ sass_getopt_register_option(getopt, '\0', "term-colors", "Enable or disable terminal ANSI color output.", true, nullptr, false, nullptr, getopt_set_term_colors); + /* bool */ sass_getopt_register_option(getopt, '\0', "quiet", "Do not print any warnings to stderr.", false, nullptr, false, nullptr, getopt_set_suppress_stderr); + + } + // EO sass_getopt_populate_options + + // Utility function to tell LibSass to register its default arguments + void ADDCALL sass_getopt_populate_arguments(struct SassGetOpt* getopt) + { + sass_getopt_register_argument(getopt, false, "INPUT_FILE|--", cli_sass_compiler_input_file_arg); + sass_getopt_register_argument(getopt, true, "OUTPUT_FILE|--", cli_sass_compiler_output_file_arg); + } + // EO sass_getopt_populate_arguments + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + +} diff --git a/src/capi_getopt.hpp b/src/capi_getopt.hpp new file mode 100644 index 0000000000..d5be874476 --- /dev/null +++ b/src/capi_getopt.hpp @@ -0,0 +1,15 @@ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#ifndef SASS_CAPI_GETOPT_HPP +#define SASS_CAPI_GETOPT_HPP + +// sass.hpp must go before all system headers +// to get the __EXTENSIONS__ fix on Solaris. +#include "capi_sass.hpp" + +#include + +// No private C-API implementation details yet + +#endif diff --git a/src/capi_import.cpp b/src/capi_import.cpp new file mode 100644 index 0000000000..3cce2bf6e2 --- /dev/null +++ b/src/capi_import.cpp @@ -0,0 +1,196 @@ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#include "capi_import.hpp" + +#include "import.hpp" +#include "sources.hpp" + +extern "C" { + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + using namespace Sass; + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // Create import entry by reading from `stdin`. + struct SassImport* ADDCALL sass_make_stdin_import(const char* path) + { + std::istreambuf_iterator begin(std::cin), end; + sass::string text(begin, end); // consume everything + Import* import = SASS_MEMORY_NEW(Import, + SASS_MEMORY_NEW(SourceString, + path ? path : "stream://stdin", + std::move(text)), + SASS_IMPORT_AUTO); + import->refcount += 1; + return import->wrap(); + } + // EO sass_make_stdin_import + + // Create import entry to load the passed input path. + struct SassImport* ADDCALL sass_make_file_import(const char* imp_path) + { + sass::string abs_path(File::rel2abs(imp_path, CWD())); + Import* loaded = SASS_MEMORY_NEW(Import, + SASS_MEMORY_NEW(SourceFile, + imp_path, abs_path.c_str(), + nullptr, nullptr), + SASS_IMPORT_AUTO); + loaded->refcount += 1; + return loaded->wrap(); + } + // EO sass_make_file_import + + // Create import entry for the passed data with optional path. + // Note: we take ownership of the passed `content` memory. + struct SassImport* ADDCALL sass_make_content_import(char* content, const char* path) + { + Import* loaded = SASS_MEMORY_NEW(Import, + SASS_MEMORY_NEW(SourceString, + path ? path : "stream://stdin", + path ? path : "stream://stdin", + content ? content : "", + ""), + SASS_IMPORT_AUTO); + loaded->refcount += 1; + return loaded->wrap(); + } + // EO sass_make_content_import + + // Create single import entry returned by the custom importer inside the list. + // Note: source/srcmap can be empty to let LibSass do the file resolving. + // Note: we take ownership of the passed `source` and `srcmap` memory. + struct SassImport* ADDCALL sass_make_import(const char* imp_path, const char* abs_path, + char* source, char* srcmap, enum SassImportSyntax format) + { + Import* import = + SASS_MEMORY_NEW(Import, + SASS_MEMORY_NEW(SourceFile, + imp_path, abs_path, + source ? source : 0, + srcmap ? srcmap : 0), + format); + // Use reference counter + import->refcount += 1; + // Create the shared source object + return Import::wrap(import); + } + // EO sass_make_import + + // Just in case we have some stray import structs + void ADDCALL sass_delete_import(struct SassImport* import) + { + Import& object = Import::unwrap(import); + if (object.refcount <= 1) { + object.refcount = 0; + delete &object; + } + else { + object.refcount -= 1; + } + } + // EO sass_delete_import + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // Getter for specific import format for the given import (force css/sass/scss or set to auto) + enum SassImportSyntax ADDCALL sass_import_get_type(const struct SassImport* entry) + { + return Import::unwrap(entry).syntax; + } + + // Setter for specific import format for the given import (force css/sass/scss or set to auto) + void ADDCALL sass_import_set_syntax(struct SassImport* import, enum SassImportSyntax syntax) + { + Import::unwrap(import).syntax = syntax; + } + + // Getter for original import path (as seen when parsed) + const char* ADDCALL sass_import_get_imp_path(const struct SassImport* entry) + { + return Import::unwrap(entry).getImpPath(); + } + + // Getter for resolve absolute path (after being resolved) + const char* ADDCALL sass_import_get_abs_path(const struct SassImport* entry) + { + return Import::unwrap(entry).getAbsPath(); + } + + // Getter for import error message (used by custom importers). + // If error is not `nullptr`, the import must be considered as failed. + const char* ADDCALL sass_import_get_error_message(struct SassImport* entry) + { + return Import::unwrap(entry).getErrorMsg(); + } + + // Setter for import error message (used by custom importers). + // If error is not `nullptr`, the import must be considered as failed. + void ADDCALL sass_import_set_error_message(struct SassImport* entry, const char* msg) + { + return Import::unwrap(entry).setErrorMsg(msg); + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // Create new list container for imports. + struct SassImportList* ADDCALL sass_make_import_list() + { + return new SassImportList; + } + + // Release memory of list container and all children. + void ADDCALL sass_delete_import_list(struct SassImportList* list) + { + if (list == nullptr) return; + for (auto import : *list) { + sass_delete_import(import); + } + delete list; + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // Return number of items currently in the list. + size_t ADDCALL sass_import_list_size(struct SassImportList* list) + { + if (list == nullptr) return 0; + return list->size(); + } + + // Remove and return first item in the list (as in a fifo queue). + struct SassImport* ADDCALL sass_import_list_shift(struct SassImportList* list) + { + if (list == nullptr) return nullptr; + if (list->empty()) return nullptr; + auto ptr = list->front(); + list->pop_front(); + return ptr; + } + + // Append additional import to the list container. + void ADDCALL sass_import_list_push(struct SassImportList* list, struct SassImport* import) + { + if (list == nullptr) return; + if (import) list->push_back(import); + } + + // Append additional import to the list container and takes ownership of the import. + void ADDCALL sass_import_list_emplace(struct SassImportList* list, struct SassImport* import) + { + sass_import_list_push(list, import); + // Reduce refcount by one (we took ownership) + if (import) Import::unwrap(import).refcount--; + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + +} diff --git a/src/capi_import.hpp b/src/capi_import.hpp new file mode 100644 index 0000000000..18b52c1816 --- /dev/null +++ b/src/capi_import.hpp @@ -0,0 +1,18 @@ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#ifndef SASS_CAPI_IMPORT_HPP +#define SASS_CAPI_IMPORT_HPP + +// sass.hpp must go before all system headers +// to get the __EXTENSIONS__ fix on Solaris. +#include "capi_sass.hpp" + +#include + +// Some structures are simple c++ vectors/queues. +// There might be a more efficient way to achieve this? +// Although compiler optimization should see this case easily! +struct SassImportList : std::deque {}; + +#endif diff --git a/src/capi_importer.cpp b/src/capi_importer.cpp new file mode 100644 index 0000000000..94d0dbaeca --- /dev/null +++ b/src/capi_importer.cpp @@ -0,0 +1,59 @@ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#include "capi_importer.hpp" + +extern "C" { + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + using namespace Sass; + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // Create custom importer (with arbitrary pointer called `cookie`) + // The pointer is often used to store the callback into the actual binding. + struct SassImporter* ADDCALL sass_make_importer(SassImporterLambda lambda, double priority, void* cookie) + { + if (lambda == nullptr) return nullptr; + struct SassImporter* importer = new SassImporter{}; + if (importer == nullptr) return nullptr; + importer->lambda = lambda; + importer->priority = priority; + importer->cookie = cookie; + return importer; + } + + // Deallocate the importer and release memory + void ADDCALL sass_delete_importer(struct SassImporter* importer) + { + delete importer; + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // Getter for importer lambda function (the one being actually invoked) + SassImporterLambda ADDCALL sass_importer_get_lambda(struct SassImporter* importer) + { + return importer->lambda; + } + + // Getter for importer priority (lowest priority is invoked first) + double ADDCALL sass_importer_get_priority(struct SassImporter* importer) + { + return importer->priority; + } + + // Getter for arbitrary cookie (used by implementers to store stuff) + void* ADDCALL sass_importer_get_cookie(struct SassImporter* importer) + { + return importer->cookie; + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + +} diff --git a/src/capi_importer.hpp b/src/capi_importer.hpp new file mode 100644 index 0000000000..6b047e72ca --- /dev/null +++ b/src/capi_importer.hpp @@ -0,0 +1,27 @@ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#ifndef SASS_CAPI_IMPORTER_HPP +#define SASS_CAPI_IMPORTER_HPP + +// sass.hpp must go before all system headers +// to get the __EXTENSIONS__ fix on Solaris. +#include "capi_sass.hpp" + +#include + +// Struct to hold importer callback +struct SassImporter { + + // The C function to be invoked + SassImporterLambda lambda; + + // Invocation priority (order) + double priority; + + // Arbitrary data cookie + void* cookie; + +}; + +#endif diff --git a/src/capi_sass.cpp b/src/capi_sass.cpp new file mode 100644 index 0000000000..237471d296 --- /dev/null +++ b/src/capi_sass.cpp @@ -0,0 +1,111 @@ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#include "capi_sass.hpp" + +#include +#include "terminal.hpp" +#include "file.hpp" + +extern "C" { + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + using namespace Sass; + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // Change the current working directory + // LibSass will fetch this once initially + // Underlying `CWD` is a thread-local var + void ADDCALL sass_chdir(const char* path) + { + if (path != nullptr) { + set_cwd(File::rel2abs(path, CWD()) + "/"); + } + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // LibSass function to print to stderr terminal output + // This function is able to print a line with colors + // It translates Unix/ANSI terminal codes to windows + void ADDCALL sass_print_stderr(const char* message) + { + Terminal::print(message, true); + } + + // LibSass function to print to stdout terminal output + // This function is able to print a line with colors + // It translates Unix/ANSI terminal codes to windows + void ADDCALL sass_print_stdout(const char* message) + { + Terminal::print(message, false); + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // Allocate a memory block on the heap of (at least) [size]. + // Caller must ensure to release acquired memory at some later point via + // `sass_free_memory`. You need to go through this utility function in + // case your code and LibSass use different memory manager implementations. + // See https://stackoverflow.com/questions/1518711/how-does-free-know-how-much-to-free + void* ADDCALL sass_alloc_memory(size_t size) + { + void* ptr = malloc(size); + if (ptr == NULL) { + std::cerr << "Out of memory.\n"; + exit(EXIT_FAILURE); + } + return ptr; + } + + // Allocate a new memory block and copy the passed string into it. + // Caller must ensure to release acquired memory at some later point via + // `sass_free_c_string`. You need to go through this utility function in + // case your code and LibSass use different memory manager implementations. + // See https://stackoverflow.com/questions/1518711/how-does-free-know-how-much-to-free + char* ADDCALL sass_copy_c_string(const char* string) + { + if (string == nullptr) return nullptr; + size_t len = ::strlen(string) + 1; + char* cpy = (char*)sass_alloc_memory(len); + ::memcpy(cpy, string, len); + return cpy; + } + + // Deallocate libsass heap memory + void ADDCALL sass_free_memory(void* ptr) + { + if (ptr) free(ptr); + } + + // Deallocate libsass heap memory + void ADDCALL sass_free_c_string(char* ptr) + { + if (ptr) free(ptr); + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // Get compiled libsass version (hard-coded) + const char* ADDCALL libsass_version(void) + { + return LIBSASS_VERSION; + } + + // Return compiled libsass language (hard-coded) + const char* ADDCALL libsass_language_version(void) + { + return LIBSASS_LANGUAGE_VERSION; + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + +} diff --git a/src/capi_sass.hpp b/src/capi_sass.hpp new file mode 100644 index 0000000000..78f769146a --- /dev/null +++ b/src/capi_sass.hpp @@ -0,0 +1,285 @@ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#ifndef SASS_CAPI_SASS_HPP +#define SASS_CAPI_SASS_HPP + +// This include must be the first in all compile units! +// Otherwise you may run into issues with other headers! + +// #define DEBUG_SHARED_PTR + +// Undefine extensions macro to tell sys includes +// that we do not want any macros to be exported +// mainly fixes an issue on SmartOS (SEC macro) +#undef __EXTENSIONS__ + +#ifdef _MSC_VER +#pragma warning(disable : 26812) +#endif + +// applies to MSVC and MinGW +#ifdef _WIN32 +// we do not want the ERROR macro +# ifndef NOGDI +# define NOGDI +# endif +// we do not want the min/max macro +# ifndef NOMINMAX +# define NOMINMAX +# endif +// we do not want the IN/OUT macro +# ifndef _NO_W32_PSEUDO_MODIFIERS +# define _NO_W32_PSEUDO_MODIFIERS +# endif +#endif + +// should we be case insensitive +// when dealing with files or paths +#ifndef FS_CASE_SENSITIVITY +# ifdef _WIN32 +# define FS_CASE_SENSITIVITY 0 +# else +# define FS_CASE_SENSITIVITY 1 +# endif +#endif + +// path separation char +#ifndef PATH_SEP +# ifdef _WIN32 +# define PATH_SEP ';' +# else +# define PATH_SEP ':' +# endif +#endif + +// OS specific line feed +// since std::endl flushes +#ifndef STRMLF +# ifdef _WIN32 +# define STRMLF '\n' +# else +# define STRMLF '\n' +# endif +#endif + +// Include C-API headers +#include "sass/base.h" +#include "sass/version.h" + +// Include allocator +#include "memory.hpp" + +// Include random seed +#include "randomize.hpp" + +// Include unordered map implementation +#ifdef USE_TSL_HOPSCOTCH +#include "tessil/hopscotch_map.h" +#include "tessil/hopscotch_set.h" +#define UnorderedMap tsl::hopscotch_map +#define UnorderedSet tsl::hopscotch_set +#else +#include +#include +#define UnorderedMap std::unordered_map +#define UnorderedSet std::unordered_set +#endif + +// Always use tessil implementation +#include "tessil/ordered_map.h" +#define OrderedMap tsl::ordered_map + +// Small helper to avoid typing +#define NPOS std::string::npos + +// output behaviors +namespace Sass { + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + const double PI = std::acos(-1); + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // sass inspect options + class InspectOptions + { + public: + + // Change default input syntax for entry point + // Only applied if entry point has AUTO syntax + enum SassImportSyntax input_syntax; + + // Output style for the generated CSS code + // A value from above SASS_STYLE_* constants + enum SassOutputStyle output_style; + + // Precision for fractional numbers + int precision; + + // Number format for sprintf. + // Cached to speed up output. + char nr_sprintf[32]; + + // Update precision and epsilon etc. + void setPrecision(int precision) + { + this->precision = precision; + // Update sprintf format to match precision + snprintf(this->nr_sprintf, 32, "%%.%df", precision); + } + + // initialization list (constructor with defaults) + InspectOptions( + enum SassOutputStyle style = SASS_STYLE_NESTED, + int precision = SassDefaultPrecision) : + input_syntax(SASS_IMPORT_AUTO), + output_style(style), + precision(precision) + { + // Update sprintf format to match precision + snprintf(nr_sprintf, 32, "%%.%df", precision); + } + + }; + // EO class InspectOptions + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // sass source-map options + class SrcMapOptions + { + public: + + // Case 1: create no source-maps + // Case 2: create source-maps, but no reference in css + // Case 3: create source-maps, reference to file in css + // Case 4: create source-maps, embed the json in the css + // Note: Writing source-maps to disk depends on implementor + enum SassSrcMapMode mode; + + // Flag to embed full sources + // Ignored for SASS_SRCMAP_NONE + bool embed_contents; + + // create file URLs for sources + bool file_urls; + + // Flags to enable more details + bool enable_openers; + bool enable_closers; + + // Directly inserted in source maps + sass::string root; + + // Path where source map is saved + sass::string path; + + // Path to file that loads us + sass::string origin; + + // Init everything to false + SrcMapOptions() : + mode(SASS_SRCMAP_NONE), + embed_contents(false), + file_urls(false), + enable_openers(false), + enable_closers(false) + {} + + }; + // EO class SrcMapOptions + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // sass output and inspect options + class OutputOptions : public InspectOptions + { + public: + // String to be used for indentation + const char* indent; + // String to be used to for line feeds + const char* linefeed; + + // Emit comments in the generated CSS indicating + // the corresponding source line. + bool source_comments; + + // Enable to not print anything on stderr (quiet mode) + bool suppress_stderr = false; + + // Sourcemap related options + SrcMapOptions mapopt; + + // initialization list (constructor with defaults) + OutputOptions(const InspectOptions& opt, + const char* indent = " ", + const char* linefeed = "\n", + bool source_comments = false) : + InspectOptions(opt), + indent(indent), linefeed(linefeed), + source_comments(source_comments) + { } + + // initialization list (constructor with defaults) + OutputOptions(SassOutputStyle style = SASS_STYLE_NESTED, + int precision = SassDefaultPrecision, + const char* indent = " ", + const char* linefeed = "\n", + bool source_comments = false) : + InspectOptions(style, precision), + indent(indent), linefeed(linefeed), + source_comments(source_comments) + { } + + }; + // EO class OutputOptions + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // helper to aid dreaded MSVC debug mode + // see implementation for more details + inline char* sass_copy_string(sass::string str) { + // In MSVC the following can lead to segfault: + // sass_copy_c_string(stream.str().c_str()); + // Reason is that the string returned by str() is disposed before + // sass_copy_c_string is invoked. The string is actually a stack + // object, so indeed nobody is holding on to it. So it seems + // perfectly fair to release it right away. So the const char* + // by c_str will point to invalid memory. I'm not sure if this is + // the behavior for all compiler, but I'm pretty sure we would + // have gotten more issues reported if that would be the case. + // Wrapping it in a functions seems the cleanest approach as the + // function must hold on to the stack variable until it's done. + return sass_copy_c_string(str.c_str()); + } + // EO sass_copy_string + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + +}; + +///////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////// + +#ifdef NDEBUG +#define SASS_ASSERT(cond, msg) ((void)0) +#else +#ifdef DEBUG +#define SASS_ASSERT(cond, msg) assert(cond && msg) +#else +#define SASS_ASSERT(cond, msg) ((void)0) +#endif +#endif + +///////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////// + +#endif diff --git a/src/capi_traces.cpp b/src/capi_traces.cpp new file mode 100644 index 0000000000..18f403c985 --- /dev/null +++ b/src/capi_traces.cpp @@ -0,0 +1,121 @@ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#include "capi_traces.hpp" + +#include "source_map.hpp" + +extern "C" { + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + using namespace Sass; + + ///////////////////////////////////////////////////////////////////////// + // Implementation related to struct SassTrace + ///////////////////////////////////////////////////////////////////////// + + // Getter for name of this trace (normally the function name or empty). + const char* ADDCALL sass_trace_get_name(struct SassTrace* trace) + { + return Traced::unwrap(trace).getName().c_str(); + } + + // Getter to check if trace is from a function call (otherwise import). + bool ADDCALL sass_trace_was_fncall(struct SassTrace* trace) + { + return Traced::unwrap(trace).isFn(); + } + + // Getter for the SourceSpan (aka ParserState) for further details + const struct SassSrcSpan* ADDCALL sass_trace_get_srcspan(struct SassTrace* trace) + { + return SourceSpan::wrap(&Traced::unwrap(trace).getPstate()); + } + + ///////////////////////////////////////////////////////////////////////// + // Implementation related to struct SassSrcSpan + ///////////////////////////////////////////////////////////////////////// + + // Getter for line position of trace (starting from 0) + size_t ADDCALL sass_srcspan_get_src_ln(struct SassSrcSpan* pstate) + { + return SourceSpan::unwrap(pstate).position.line; + } + + // Getter for column position of trace (starting from 0) + size_t ADDCALL sass_srcspan_get_src_col(struct SassSrcSpan* pstate) + { + return SourceSpan::unwrap(pstate).position.column; + } + + // Getter for line position of trace (starting from 1) + size_t ADDCALL sass_srcspan_get_src_line(struct SassSrcSpan* pstate) + { + return SourceSpan::unwrap(pstate).getLine(); + } + + // Getter for column position of trace (starting from 1) + size_t ADDCALL sass_srcspan_get_src_column(struct SassSrcSpan* pstate) + { + return SourceSpan::unwrap(pstate).getColumn(); + } + + // Getter for line span of trace (starting from 0) + size_t ADDCALL sass_srcspan_get_span_ln(struct SassSrcSpan* pstate) + { + return SourceSpan::unwrap(pstate).span.line; + } + + // Getter for column span of trace (starting from 0) + size_t ADDCALL sass_srcspan_get_span_col(struct SassSrcSpan* pstate) + { + return SourceSpan::unwrap(pstate).span.column; + } + + // Getter for attached source of trace for further details + struct SassSource* ADDCALL sass_srcspan_get_source(struct SassSrcSpan* pstate) + { + return SourceData::wrap(SourceSpan::unwrap(pstate).getSource()); + } + + ///////////////////////////////////////////////////////////////////////// + // Implementation related to struct SassSource + ///////////////////////////////////////////////////////////////////////// + + // Getter for absolute path this source was loaded from. This path should + // always be absolute but there is no real hard requirement for it. Custom + // importers may use different pattern for paths. LibSass tries to support + // regular win/nix paths and urls. But we it also try to be agnostic here, + // so anything a custom importer returns will be returned here. + const char* ADDCALL sass_source_get_abs_path(struct SassSource* source) + { + return SourceData::unwrap(source).getAbsPath(); + } + + // Getter for import path this source was loaded from. This path should + // be as it was found when the import was parsed. This is merely useful + // for debugging purposes, but we keep it around anyway. + const char* ADDCALL sass_source_get_imp_path(struct SassSource* source) + { + return SourceData::unwrap(source).getImpPath(); + } + + // Getter for the loaded content attached to the source. + const char* ADDCALL sass_source_get_content(struct SassSource* source) + { + return SourceData::unwrap(source).content(); + } + + // Getter for the loaded srcmap attached to the source. + // Note: not used yet, only here for future improvements. + const char* ADDCALL sass_source_get_srcmap(struct SassSource* source) + { + return SourceData::unwrap(source).srcmaps(); + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + +} diff --git a/src/capi_traces.hpp b/src/capi_traces.hpp new file mode 100644 index 0000000000..5a88e85946 --- /dev/null +++ b/src/capi_traces.hpp @@ -0,0 +1,13 @@ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#ifndef SASS_CAPI_TRACES_HPP +#define SASS_CAPI_TRACES_HPP + +// sass.hpp must go before all system headers +// to get the __EXTENSIONS__ fix on Solaris. +#include "capi_sass.hpp" + +// No private C-API implementation details yet + +#endif diff --git a/src/capi_values.cpp b/src/capi_values.cpp new file mode 100644 index 0000000000..c966157769 --- /dev/null +++ b/src/capi_values.cpp @@ -0,0 +1,326 @@ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#include "capi_values.hpp" + +#include "exceptions.hpp" +#include "ast_values.hpp" + +extern "C" { + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + using namespace Sass; + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + struct SassMapIterator { + Hashed::ordered_map_type::iterator pos; + Hashed::ordered_map_type::iterator end; + }; + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + Map* getMap(struct SassValue* value) { return Value::unwrap(value).isaMap(); } + List* getList(struct SassValue* value) { return Value::unwrap(value).isaList(); } + Null* getNull(struct SassValue* value) { return Value::unwrap(value).isaNull(); } + Value* getValue(struct SassValue* value) { return Value::unwrap(value).isaValue(); } + Number* getNumber(struct SassValue* value) { return Value::unwrap(value).isaNumber(); } + String* getString(struct SassValue* value) { return Value::unwrap(value).isaString(); } + Boolean* getBoolean(struct SassValue* value) { return Value::unwrap(value).isaBoolean(); } + ColorRgba* getTerm(struct SassValue* value) { return Value::unwrap(value).isaColorRgba(); } + CustomError* getError(struct SassValue* value) { return Value::unwrap(value).isaCustomError(); } + CustomWarning* getWarning(struct SassValue* value) { return Value::unwrap(value).isaCustomWarning(); } + + // Return another reference to an existing value. We simply re-use the reference counted + // object and re-implement the memory handling also partially here (SharedImpl lite). + struct SassValue* newSassValue(Value* value) { value->refcount += 1; return value->wrap(); } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // Return the sass tag for a generic sass value (useful for your own case switch) + enum SassValueType ADDCALL sass_value_get_tag(struct SassValue* v) { return getValue(v)->getTag(); } + + // Check value for a specific type (dispatch to virtual check methods) + bool ADDCALL sass_value_is_null(struct SassValue* value) { return getNull(value) != nullptr; } + bool ADDCALL sass_value_is_number(struct SassValue* value) { return getNumber(value) != nullptr; } + bool ADDCALL sass_value_is_string(struct SassValue* value) { return getString(value) != nullptr; } + bool ADDCALL sass_value_is_boolean(struct SassValue* value) { return getBoolean(value) != nullptr; } + bool ADDCALL sass_value_is_color(struct SassValue* value) { return getTerm(value) != nullptr; } + bool ADDCALL sass_value_is_list(struct SassValue* value) { return getList(value) != nullptr; } + bool ADDCALL sass_value_is_map(struct SassValue* value) { return getMap(value) != nullptr; } + bool ADDCALL sass_value_is_error(struct SassValue* value) { return getError(value) != nullptr; } + bool ADDCALL sass_value_is_warning(struct SassValue* value) { return getWarning(value) != nullptr; } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // Getters and setters for Sass_Number (UB if `sass_value_is_number` is false) + double ADDCALL sass_number_get_value(struct SassValue* number) { return getNumber(number)->value(); } + void ADDCALL sass_number_set_value(struct SassValue* number, double value) { getNumber(number)->value(value); } + const char* ADDCALL sass_number_get_unit(struct SassValue* number) { return getNumber(number)->unit().c_str(); } + void ADDCALL sass_number_set_unit(struct SassValue* number, const char* unit) { getNumber(number)->unit(unit); } + // Normalize number and its units to standard units, e.g. `ms` will become `s` (useful for comparisons) + void ADDCALL sass_number_normalize(struct SassValue* number) { getNumber(number)->normalize(); } + // Reduce number and its units to a minimal form, e.g. `ms*ms/s` will become `ms` (useful for output) + void ADDCALL sass_number_reduce(struct SassValue* number) { getNumber(number)->reduce(); } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // Getters and setters for Sass_String (UB if `sass_value_is_boolean` is false) + const char* ADDCALL sass_string_get_value(struct SassValue* string) { return getString(string)->value().c_str(); } + void ADDCALL sass_string_set_value(struct SassValue* string, char* value) { getString(string)->value(value); } + bool ADDCALL sass_string_is_quoted(struct SassValue* string) { return getString(string)->hasQuotes(); } + void ADDCALL sass_string_set_quoted(struct SassValue* string, bool quoted) { getString(string)->hasQuotes(quoted); } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // Getters and setters for Sass_Boolean (UB if `sass_value_is_number` is false) + bool ADDCALL sass_boolean_get_value(struct SassValue* boolean) { return getBoolean(boolean)->value(); } + void ADDCALL sass_boolean_set_value(struct SassValue* boolean, bool value) { getBoolean(boolean)->value(value); } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // Getters and setters for Sass_Color (UB if `sass_value_is_color` is false) + double ADDCALL sass_color_get_r(struct SassValue* color) { return getTerm(color)->r(); } + void ADDCALL sass_color_set_r(struct SassValue* color, double r) { getTerm(color)->r(r); } + double ADDCALL sass_color_get_g(struct SassValue* color) { return getTerm(color)->g(); } + void ADDCALL sass_color_set_g(struct SassValue* color, double g) { getTerm(color)->g(g); } + double ADDCALL sass_color_get_b(struct SassValue* color) { return getTerm(color)->b(); } + void ADDCALL sass_color_set_b(struct SassValue* color, double b) { getTerm(color)->b(b); } + double ADDCALL sass_color_get_a(struct SassValue* color) { return getTerm(color)->a(); } + void ADDCALL sass_color_set_a(struct SassValue* color, double a) { getTerm(color)->a(a); } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // Return the value stored at the given key (or nullptr if it doesn't exist) + struct SassValue* ADDCALL sass_map_get(struct SassValue* map, struct SassValue* key) + { + auto value = getMap(map)->at(getValue(key)); + if (value.isNull()) return nullptr; + return Value::wrap(value); + } + + // Set or create the value in the map for the given key + void ADDCALL sass_map_set(struct SassValue* map, struct SassValue* key, struct SassValue* value) + { + getMap(map)->insertOrSet(getValue(key), getValue(value)); + } + + // Create an iterator to loop over all key/value pairs of this map + // This iterator will get invalid once you alter the underlying map + struct SassMapIterator* ADDCALL sass_map_make_iterator(struct SassValue* map) + { + return new SassMapIterator{ getMap(map)->begin(), getMap(map)->end() }; + } + + // Delete the iterator after you are done with it + void ADDCALL sass_map_delete_iterator(struct SassMapIterator* it) + { + delete it; + } + + // Get key for the current map iterator position + struct SassValue* ADDCALL sass_map_iterator_get_key(struct SassMapIterator* it) + { + return Value::wrap(it->pos->first); + } + + // Get value for the current map iterator position + struct SassValue* ADDCALL sass_map_iterator_get_value(struct SassMapIterator* it) + { + return Value::wrap(it->pos->second); + } + + // Returns true once the iterator has reached the end + bool ADDCALL sass_map_iterator_exhausted(struct SassMapIterator* it) + { + return it->pos == it->end; + } + + // Advance the iterator to the next key/value pair or the end + void ADDCALL sass_map_iterator_next(struct SassMapIterator* it) + { + it->pos += 1; + } + + ///////////////////////////////////////////////////////////////////////// + // ToDo: should list also have an iterator, is it so useful and cool? + // ToDo: Index access has the advantage that it is never invalidated. + ///////////////////////////////////////////////////////////////////////// + + // Getters and setters for Sass_List + size_t ADDCALL sass_list_get_size(struct SassValue* v) { return getList(v)->size(); } + + enum SassSeparator ADDCALL sass_list_get_separator(struct SassValue* v) { return getList(v)->separator(); } + void ADDCALL sass_list_set_separator(struct SassValue* v, enum SassSeparator separator) { getList(v)->separator(separator); } + bool ADDCALL sass_list_get_is_bracketed(struct SassValue* v) { return getList(v)->hasBrackets(); } + void ADDCALL sass_list_set_is_bracketed(struct SassValue* v, bool is_bracketed) { getList(v)->hasBrackets(is_bracketed); } + + void ADDCALL sass_list_push(struct SassValue* list, struct SassValue* value) { getList(list)->append(getValue(value)); } + void ADDCALL sass_list_unshift(struct SassValue* list, struct SassValue* value) { getList(list)->unshift(getValue(value)); } + struct SassValue* ADDCALL sass_list_at(struct SassValue* list, size_t i) { return Value::wrap(getList(list)->at(i)); } + struct SassValue* ADDCALL sass_list_pop(struct SassValue* list, struct SassValue* value) { return Value::wrap(getList(list)->pop()); } + struct SassValue* ADDCALL sass_list_shift(struct SassValue* list, struct SassValue* value) { return Value::wrap(getList(list)->shift()); } + + // Getters and setters for Sass_List values + struct SassValue* ADDCALL sass_list_get_value(struct SassValue* v, size_t i) { return Value::wrap(getList(v)->at(i)); } + void ADDCALL sass_list_set_value(struct SassValue* v, size_t i, struct SassValue* value) { getList(v)->set(i, getValue(value)); } + + // Getters and setters for Sass_Error + const char* ADDCALL sass_error_get_message(struct SassValue* v) { return getError(v)->message().c_str(); }; + void ADDCALL sass_error_set_message(struct SassValue* v, const char* msg) { getError(v)->message(msg); }; + + // Getters and setters for Sass_Warning + const char* ADDCALL sass_warning_get_message(struct SassValue* v) { return getWarning(v)->message().c_str(); }; + void ADDCALL sass_warning_set_message(struct SassValue* v, const char* msg) { getWarning(v)->message(msg); }; + + + ///////////////////////////////////////////////////////////////////////// + // Constructor functions for all value types + ///////////////////////////////////////////////////////////////////////// + + struct SassValue* ADDCALL sass_make_boolean(bool state) + { + return newSassValue(SASS_MEMORY_NEW( + Boolean, SourceSpan::internal("sass://boolean"), state)); + } + + struct SassValue* ADDCALL sass_make_number(double value, const char* unit) + { + return newSassValue(SASS_MEMORY_NEW( + Number, SourceSpan::internal("sass://number"), value, unit ? unit : "")); + } + + struct SassValue* ADDCALL sass_make_color(double r, double g, double b, double a) + { + return newSassValue(SASS_MEMORY_NEW( + ColorRgba, SourceSpan::internal("sass://color"), r, g, b, a)); + } + + struct SassValue* ADDCALL sass_make_string(const char* value, bool is_quoted) + { + return newSassValue(SASS_MEMORY_NEW( + String, SourceSpan::internal("sass://string"), value, is_quoted)); + } + + struct SassValue* ADDCALL sass_make_list(enum SassSeparator sep, bool is_bracketed) + { + return newSassValue(SASS_MEMORY_NEW( + List, SourceSpan::internal("sass://list"), {}, sep, is_bracketed)); + } + + struct SassValue* ADDCALL sass_make_map(void) + { + return newSassValue(SASS_MEMORY_NEW( + Map, SourceSpan::internal("sass://map"))); + } + + struct SassValue* ADDCALL sass_make_null(void) + { + return newSassValue(SASS_MEMORY_NEW( + Null, SourceSpan::internal("sass://null"))); + } + + struct SassValue* ADDCALL sass_make_error(const char* msg) + { + return newSassValue(SASS_MEMORY_NEW( + CustomError, SourceSpan::internal("sass://error"), msg)); + } + + struct SassValue* ADDCALL sass_make_warning(const char* msg) + { + return newSassValue(SASS_MEMORY_NEW( + CustomWarning, SourceSpan::internal("sass://warning"), msg)); + } + + ///////////////////////////////////////////////////////////////////////// + // Decrease reference counter and eventually free memory. + // Will free all associated sass values for maps or lists. + ///////////////////////////////////////////////////////////////////////// + + void ADDCALL sass_delete_value(struct SassValue* value) + { + Value* val = getValue(value); + if (value) { + val->refcount -= 1; + if (val->refcount == 0) { + delete val; + } + } + } + + ///////////////////////////////////////////////////////////////////////// + // Make a deep cloned copy of the given sass value + ///////////////////////////////////////////////////////////////////////// + + struct SassValue* ADDCALL sass_clone_value(struct SassValue* value) + { + Value* copy = getValue(value)->copy(SASS_MEMORY_POS_VOID); + copy->cloneChildren(SASS_MEMORY_POS_VOID); + return newSassValue(copy); + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + struct SassValue* ADDCALL sass_value_stringify(struct SassValue* value, bool compressed, int precision) + { + Value* val = getValue(value); + sass::string str(val->inspect(precision)); + return sass_make_string(str.c_str(), true); + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + struct SassValue* ADDCALL sass_value_op(enum SassOperator op, struct SassValue* left, struct SassValue* right) + { + + Logger logger; + SourceSpan pstate; + Sass::ValueObj copy; + Value* lhs = getValue(left); + Value* rhs = getValue(right); + + try { + switch (op) { + case SassOperator::OR: return Value::wrap(lhs->isTruthy() ? lhs : rhs); + case SassOperator::AND: return Value::wrap(lhs->isTruthy() ? rhs : lhs); + case SassOperator::ADD: copy = lhs->plus(rhs, logger, pstate); break; + case SassOperator::SUB: copy = lhs->minus(rhs, logger, pstate); break; + case SassOperator::MUL: copy = lhs->times(rhs, logger, pstate); break; + case SassOperator::DIV: copy = lhs->dividedBy(rhs, logger, pstate); break; + case SassOperator::MOD: copy = lhs->modulo(rhs, logger, pstate); break; + case SassOperator::EQ: return sass_make_boolean(PtrObjEqualityFn(rhs, lhs)); + case SassOperator::NEQ: return sass_make_boolean(!PtrObjEqualityFn(rhs, lhs)); + case SassOperator::GT: return sass_make_boolean(lhs->greaterThan(rhs, logger, pstate)); + case SassOperator::GTE: return sass_make_boolean(lhs->greaterThanOrEquals(rhs, logger, pstate)); + case SassOperator::LT: return sass_make_boolean(lhs->lessThan(rhs, logger, pstate)); + case SassOperator::LTE: return sass_make_boolean(lhs->lessThanOrEquals(rhs, logger, pstate)); + default: throw Exception::SassScriptException("Operation not implemented.", logger, pstate); + } + copy->refcount += 1; + return copy->wrap(); + } + // simply pass the error message back to the caller for now + catch (std::bad_alloc&) { return sass_make_error("memory exhausted"); } + catch (std::exception & e) { return sass_make_error(e.what()); } + catch (sass::string & e) { return sass_make_error(e.c_str()); } + catch (const char* e) { return sass_make_error(e); } + catch (...) { return sass_make_error("unknown"); } + + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + +} diff --git a/src/capi_values.hpp b/src/capi_values.hpp new file mode 100644 index 0000000000..b0bc2677b6 --- /dev/null +++ b/src/capi_values.hpp @@ -0,0 +1,13 @@ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#ifndef SASS_CAPI_VALUES_HPP +#define SASS_CAPI_VALUES_HPP + +// sass.hpp must go before all system headers +// to get the __EXTENSIONS__ fix on Solaris. +#include "capi_sass.hpp" + +// No private C-API implementation details yet + +#endif diff --git a/src/capi_variable.cpp b/src/capi_variable.cpp new file mode 100644 index 0000000000..9dfd729cdf --- /dev/null +++ b/src/capi_variable.cpp @@ -0,0 +1,46 @@ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#include "capi_variable.hpp" + +#include "compiler.hpp" + +using namespace Sass; + +extern "C" { + + // Getter for lexical variable (lexical to scope where function is called). + // Note: C-API function can only access existing variables and not create new ones! + struct SassValue* ADDCALL sass_env_get_lexical (struct SassCompiler* compiler, const char* name) { + EnvRef ref = Compiler::unwrap(compiler).varRoot.findVarIdx(EnvKey{ name }, "", false); + if (ref.isValid()) return Value::wrap(Compiler::unwrap(compiler).varRoot.getVariable(ref)); + return nullptr; + } + + // Setter for lexical variable (lexical to scope where function is called). + // Returns true if variable was set or false if it does not exist (we can't create it) + // Note: C-API function can only access existing variables and not create new ones! + bool ADDCALL sass_env_set_lexical (struct SassCompiler* compiler, const char* name, struct SassValue* val) { + EnvRef ref = Compiler::unwrap(compiler).varRoot.findVarIdx(EnvKey{ name }, "", false); + if (ref.isValid()) Compiler::unwrap(compiler).varRoot.setVariable(ref, &Value::unwrap(val), false); + return ref.isValid(); + } + + // Getter for global variable (only variables on the root scope are considered). + // Note: C-API function can only access existing variables and not create new ones! + struct SassValue* ADDCALL sass_env_get_global (struct SassCompiler* compiler, const char* name) { + EnvRef ref = Compiler::unwrap(compiler).varRoot.findVarIdx(EnvKey{ name }, "", true); + if (ref.isValid()) return Value::wrap(Compiler::unwrap(compiler).varRoot.getVariable(ref)); + return nullptr; + } + + // Setter for global variable (only variables on the root scope are considered). + // Returns true if variable was set or false if it does not exist (we can't create it) + // Note: C-API function can only access existing variables and not create new ones! + bool ADDCALL sass_env_set_global (struct SassCompiler* compiler, const char* name, struct SassValue* val) { + EnvRef ref = Compiler::unwrap(compiler).varRoot.findVarIdx(EnvKey{ name }, "", true); + if (ref.isValid()) Compiler::unwrap(compiler).varRoot.setVariable(ref, &Value::unwrap(val), false); + return ref.isValid(); + } + +} diff --git a/src/capi_variable.hpp b/src/capi_variable.hpp new file mode 100644 index 0000000000..0d8a7f36e1 --- /dev/null +++ b/src/capi_variable.hpp @@ -0,0 +1,13 @@ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#ifndef SASS_CAPI_VARIABLE_HPP +#define SASS_CAPI_VARIABLE_HPP + +// sass.hpp must go before all system headers +// to get the __EXTENSIONS__ fix on Solaris. +#include "capi_sass.hpp" + +// No private C-API implementation details yet + +#endif diff --git a/src/cencode.c b/src/cencode.c deleted file mode 100644 index 3932fcc3e2..0000000000 --- a/src/cencode.c +++ /dev/null @@ -1,106 +0,0 @@ -/* -cencoder.c - c source to a base64 encoding algorithm implementation - -This is part of the libb64 project, and has been placed in the public domain. -For details, see http://sourceforge.net/projects/libb64 -*/ - -#include "b64/cencode.h" - -void base64_init_encodestate(base64_encodestate* state_in) -{ - state_in->step = step_A; - state_in->result = 0; - state_in->stepcount = 0; -} - -char base64_encode_value(char value_in) -{ - static const char* encoding = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; - if (value_in > 63) return '='; - return encoding[(int)value_in]; -} - -int base64_encode_block(const char* plaintext_in, int length_in, char* code_out, base64_encodestate* state_in) -{ - const char* plainchar = plaintext_in; - const char* const plaintextend = plaintext_in + length_in; - char* codechar = code_out; - char result; - char fragment; - - result = state_in->result; - - switch (state_in->step) - { - while (1) - { - case step_A: - if (plainchar == plaintextend) - { - state_in->result = result; - state_in->step = step_A; - return (int)(codechar - code_out); - } - fragment = *plainchar++; - result = (fragment & 0x0fc) >> 2; - *codechar++ = base64_encode_value(result); - result = (fragment & 0x003) << 4; - /* fall through */ - - case step_B: - if (plainchar == plaintextend) - { - state_in->result = result; - state_in->step = step_B; - return (int)(codechar - code_out); - } - fragment = *plainchar++; - result |= (fragment & 0x0f0) >> 4; - *codechar++ = base64_encode_value(result); - result = (fragment & 0x00f) << 2; - /* fall through */ - - case step_C: - if (plainchar == plaintextend) - { - state_in->result = result; - state_in->step = step_C; - return (int)(codechar - code_out); - } - fragment = *plainchar++; - result |= (fragment & 0x0c0) >> 6; - *codechar++ = base64_encode_value(result); - result = (fragment & 0x03f) >> 0; - *codechar++ = base64_encode_value(result); - - ++(state_in->stepcount); - } - } - /* control should not reach here */ - return (int)(codechar - code_out); -} - -int base64_encode_blockend(char* code_out, base64_encodestate* state_in) -{ - char* codechar = code_out; - - switch (state_in->step) - { - case step_B: - *codechar++ = base64_encode_value(state_in->result); - *codechar++ = '='; - *codechar++ = '='; - break; - case step_C: - *codechar++ = base64_encode_value(state_in->result); - *codechar++ = '='; - break; - case step_A: - break; - } - *codechar++ = '\n'; - - return (int)(codechar - code_out); -} - diff --git a/src/cencode.cpp b/src/cencode.cpp new file mode 100644 index 0000000000..5bff953a41 --- /dev/null +++ b/src/cencode.cpp @@ -0,0 +1,115 @@ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +/* cencode.c - c source to a base64 encoding algorithm implementation */ +/* Part of the libb64 project, and has been placed in the public domain. */ +/* For details, see http://sourceforge.net/projects/libb64 */ +/*****************************************************************************/ + +#include "b64/cencode.hpp" + +namespace base64 { + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + void base64_init_encodestate(base64_encodestate* state_in) + { + state_in->step = step_A; + state_in->result = 0; + state_in->stepcount = 0; + } + + char base64_encode_value(char value_in) + { + static const char* encoding = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + if (value_in > 63) return '='; + return encoding[(int)value_in]; + } + + int base64_encode_block(const char* plaintext_in, int length_in, char* code_out, base64_encodestate* state_in) + { + const char* plainchar = plaintext_in; + const char* const plaintextend = plaintext_in + length_in; + char* codechar = code_out; + char result; + char fragment; + + result = state_in->result; + + switch (state_in->step) + { + while (1) + { + case step_A: + if (plainchar == plaintextend) + { + state_in->result = result; + state_in->step = step_A; + return (int)(codechar - code_out); + } + fragment = *plainchar++; + result = (fragment & 0x0fc) >> 2; + *codechar++ = base64_encode_value(result); + result = (fragment & 0x003) << 4; + /* fall through */ + + case step_B: + if (plainchar == plaintextend) + { + state_in->result = result; + state_in->step = step_B; + return (int)(codechar - code_out); + } + fragment = *plainchar++; + result |= (fragment & 0x0f0) >> 4; + *codechar++ = base64_encode_value(result); + result = (fragment & 0x00f) << 2; + /* fall through */ + + case step_C: + if (plainchar == plaintextend) + { + state_in->result = result; + state_in->step = step_C; + return (int)(codechar - code_out); + } + fragment = *plainchar++; + result |= (fragment & 0x0c0) >> 6; + *codechar++ = base64_encode_value(result); + result = (fragment & 0x03f) >> 0; + *codechar++ = base64_encode_value(result); + + ++(state_in->stepcount); + } + } + /* control should not reach here */ + return (int)(codechar - code_out); + } + + int base64_encode_blockend(char* code_out, base64_encodestate* state_in) + { + char* codechar = code_out; + + switch (state_in->step) + { + case step_B: + *codechar++ = base64_encode_value(state_in->result); + *codechar++ = '='; + *codechar++ = '='; + break; + case step_C: + *codechar++ = base64_encode_value(state_in->result); + *codechar++ = '='; + break; + case step_A: + break; + } + + return (int)(codechar - code_out); + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + +} diff --git a/src/character.cpp b/src/character.cpp new file mode 100644 index 0000000000..d3b5d5dc64 --- /dev/null +++ b/src/character.cpp @@ -0,0 +1,97 @@ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#include "character.hpp" + +namespace Sass +{ + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + namespace Character + { + + const std::bitset<256> tblNewline( + "00000000000000000000000000000000" // 255 - 224 + "00000000000000000000000000000000" // 223 - 192 + "00000000000000000000000000000000" // 192 - 160 + "00000000000000000000000000000000" // 159 - 128 + "00000000000000000000000000000000" // 127 - 96 + "00000000000000000000000000000000" // 95 - 64 + "00000000000000000000000000000000" // 63 - 32 + "00000000000000000011010000000000" // 31 - 0 + ); + + const std::bitset<256> tblSpaceOrTab( + "00000000000000000000000000000000" // 255 - 224 + "00000000000000000000000000000000" // 223 - 192 + "00000000000000000000000000000000" // 192 - 160 + "00000000000000000000000000000000" // 159 - 128 + "00000000000000000000000000000000" // 127 - 96 + "00000000000000000000000000000000" // 95 - 64 + "00000000000000000000000000000001" // 63 - 32 + "00000000000000000000101000000000" // 31 - 0 + ); + + const std::bitset<256> tblWhitespace( + "00000000000000000000000000000000" // 255 - 224 + "00000000000000000000000000000000" // 223 - 192 + "00000000000000000000000000000000" // 192 - 160 + "00000000000000000000000000000000" // 159 - 128 + "00000000000000000000000000000000" // 127 - 96 + "00000000000000000000000000000000" // 95 - 64 + "00000000000000000000000000000001" // 63 - 32 + "00000000000000000011111000000000" // 31 - 0 + ); + + const std::bitset<256> tblAlphabetic( + "00000000000000000000000000000000" // 255 - 224 + "00000000000000000000000000000000" // 223 - 192 + "00000000000000000000000000000000" // 192 - 160 + "00000000000000000000000000000000" // 159 - 128 + "00000111111111111111111111111110" // 127 - 96 + "00000111111111111111111111111110" // 95 - 64 + "00000000000000000000000000000000" // 63 - 32 + "00000000000000000000000000000000" // 31 - 0 + ); + + const std::bitset<256> tblDigit( + "00000000000000000000000000000000" // 255 - 224 + "00000000000000000000000000000000" // 223 - 192 + "00000000000000000000000000000000" // 192 - 160 + "00000000000000000000000000000000" // 159 - 128 + "00000000000000000000000000000000" // 127 - 96 + "00000000000000000000000000000000" // 95 - 64 + "00000011111111110000000000000000" // 63 - 32 + "00000000000000000000000000000000" // 31 - 0 + ); + + const std::bitset<256> tblAlphanumeric( + "00000000000000000000000000000000" // 255 - 224 + "00000000000000000000000000000000" // 223 - 192 + "00000000000000000000000000000000" // 192 - 160 + "00000000000000000000000000000000" // 159 - 128 + "00000111111111111111111111111110" // 127 - 96 + "00000111111111111111111111111110" // 95 - 64 + "00000011111111110000000000000000" // 63 - 32 + "00000000000000000000000000000000" // 31 - 0 + ); + + const std::bitset<256> tblHex( + "00000000000000000000000000000000" // 255 - 224 + "00000000000000000000000000000000" // 223 - 192 + "00000000000000000000000000000000" // 192 - 160 + "00000000000000000000000000000000" // 159 - 128 + "00000000000000000000000001111110" // 127 - 96 + "00000000000000000000000001111110" // 95 - 64 + "00000011111111110000000000000000" // 63 - 32 + "00000000000000000000000000000000" // 31 - 0 + ); + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + } + +} diff --git a/src/character.hpp b/src/character.hpp new file mode 100644 index 0000000000..8322082627 --- /dev/null +++ b/src/character.hpp @@ -0,0 +1,234 @@ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#ifndef SASS_CHARACTER_HPP +#define SASS_CHARACTER_HPP + +// sass.hpp must go before all system headers +// to get the __EXTENSIONS__ fix on Solaris. +#include "capi_sass.hpp" + +#include +#include "charcode.hpp" + +namespace Sass { + + namespace Character { + + using namespace Charcode; + + // The difference between upper- and lowercase ASCII letters. + // `0b100000` can be bitwise-ORed with uppercase ASCII letters + // to get their lowercase equivalents. + const uint8_t asciiCaseBit = 0x20; + + // The ASCII lookup tables + extern const std::bitset<256> tblNewline; + extern const std::bitset<256> tblSpaceOrTab; + extern const std::bitset<256> tblWhitespace; + extern const std::bitset<256> tblAlphabetic; + extern const std::bitset<256> tblDigit; + extern const std::bitset<256> tblAlphanumeric; + extern const std::bitset<256> tblHex; + + // skip over 10xxxxxx and 01xxxxxx + // count ASCII and initial utf8 bytes + inline bool isCharacter(uint8_t character) { + // Ignore all `10xxxxxx` chars + // '0xxxxxxx' are ASCII chars + // '11xxxxxx' are utf8 starts + // 64 => initial utf8 byte + // 128 => regular ASCII char + return (character & 192) != 128; + // return (character & (128|64)) != 0; + } + + // Returns whether [character] is + // starting a utf8 multi-byte sequence. + inline bool isUtf8StartByte(uint8_t character) { + return (character & 192) == 192; + } + + // Returns whether [character] is + // part of a utf8 multi-byte sequence. + inline bool isUtf8Continuation(uint8_t character) { + return (character & 192) == 128; + } + + // Returns whether [character] is the + // beginning of a UTF-16 surrogate pair. + // bool isUtf8HighSurrogate(uint16_t character) { + // return character >= 0xD800 && character <= 0xDBFF; + // } + + // Returns whether [character] is an ASCII newline. + inline bool isNewline(uint8_t character) { + return tblNewline[character]; + } + + // Returns whether [character] is a space or a tab character. + inline bool isSpaceOrTab(uint8_t character) { + return tblSpaceOrTab[character]; + } + + // Returns whether [character] is an ASCII whitespace character. + inline bool isWhitespace(uint8_t character) { + return tblWhitespace[character]; + } + + // Returns whether [character] is a letter. + inline bool isAlphabetic(uint8_t character) { + return tblAlphabetic[character]; + } + + // Returns whether [character] is a number. + inline bool isDigit(uint8_t character) { + return tblDigit[character]; + } + + // Returns whether [character] is a letter or number. + inline bool isAlphanumeric(uint8_t character) { + return tblAlphanumeric[character]; + } + + // Returns whether [character] is legal as the start of a Sass identifier. + inline bool isNameStart(uint32_t character) { + return character == $_ + || character >= 0x0080 // 127 + || tblAlphabetic[character]; + } + + // Returns whether [character] is legal as the start of a Sass identifier. + inline bool isNameStart(uint8_t character) { + return character == $_ + || character >= 0x0080 // 127 + || tblAlphabetic[character]; + } + + // Returns whether [character] is legal in the body of a Sass identifier. + inline bool isName(uint32_t character) { + return character == $_ + || character == $minus + || character >= 0x0080 // 127 + || tblAlphanumeric[character]; + } + + // Returns whether [character] is legal in the body of a Sass identifier. + inline bool isName(uint8_t ascii) { + return ascii == $_ + || ascii == $minus + || ascii >= 0x0080 // 127 + || tblAlphanumeric[ascii]; + } + + // Returns whether [character] is a hexadecimal digit. + inline bool isHex(uint8_t ascii) { + return tblHex[ascii]; + } + + // Returns whether [character] can start a simple + // selector other than a type selector. + inline bool isSimpleSelectorStart(uint8_t character) + { + return character == $asterisk + || character == $lbracket + || character == $dot + || character == $hash + || character == $percent + || character == $colon; + } + + // Returns the value of [character] as a hex digit. + // Assumes that [character] is a hex digit. + inline uint8_t asHex(uint8_t character) + { + // assert(isHex(character)); + if (character <= $9) return character - $0; + if (character <= $F) return 10 + character - $A; + return 10 + character - $a; + } + + // Returns the hexadecimal digit for [number]. + // Assumes that [number] is less than 16. + inline uint8_t hexCharFor(uint8_t number) + { + // assert(number < 0x10); + return number < 0xA ? $0 + number + : $a - 0xA + number; + } + + // Returns the value of [character] as a decimal digit. + // Assumes that [character] is a decimal digit. + inline double asDecimal(uint8_t character) + { + // assert(character >= $0 && character <= $9); + return character - $0; + } + + // Returns the decimal digit for [number]. + // Assumes that [number] is less than 10. + inline uint8_t decimalCharFor(uint8_t number) + { + // assert(number < 10); + return $0 + number; + } + + // Assumes that [character] is a left-hand brace-like + // character, and returns the right-hand version. + inline uint8_t opposite(uint8_t character) + { + switch (character) { + case $lparen: + return $rparen; + case $lbrace: + return $rbrace; + case $lbracket: + return $rbracket; + default: + return 0; + } + } + + // Returns [character], converted to upper- + // case if it's an ASCII lowercase letter. + inline uint8_t toUpperCase(uint8_t character) + { + return (character >= $a && character <= $z) + ? character & ~asciiCaseBit : character; + } + + // Returns [character], converted to lower- + // case if it's an ASCII uppercase letter. + inline uint8_t toLowerCase(uint8_t character) + { + return (character >= $A && character <= $Z) + ? character | asciiCaseBit : character; + } + + // Returns whether [character1] and [character2] are the same, modulo ASCII case. + inline bool characterEqualsIgnoreCase(uint8_t character1, uint8_t character2) + { + if (character1 == character2) return true; + + // If this check fails, the characters are definitely different. If it + // succeeds *and* either character is an ASCII letter, they're equivalent. + if ((character1 ^ character2) != asciiCaseBit) return false; + + // Now we just need to verify that one of the characters is an ASCII letter. + uint8_t upperCase1 = character1 & ~asciiCaseBit; + return upperCase1 >= $A && upperCase1 <= $Z; + } + + // Like [characterEqualsIgnoreCase], but optimized for the + // fact that [letter] is known to be a lowercase ASCII letter. + inline bool equalsLetterIgnoreCase(uint8_t letter, uint8_t actual) + { + // assert(letter >= $a && letter <= $z); + return (actual | asciiCaseBit) == letter; + } + + } + +} + +#endif diff --git a/src/charcode.hpp b/src/charcode.hpp new file mode 100644 index 0000000000..d784e9b908 --- /dev/null +++ b/src/charcode.hpp @@ -0,0 +1,430 @@ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#ifndef SASS_CHARCODE_HPP +#define SASS_CHARCODE_HPP + +#include +#include "utf8/core.h" + +namespace Sass { + + namespace Charcode { + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // "Null character" control character. + const uint8_t $nul = 0x00; + + // "Start of Header" control character. + const uint8_t $soh = 0x01; + + // "Start of Text" control character. + const uint8_t $stx = 0x02; + + // "End of Text" control character. + const uint8_t $etx = 0x03; + + // "End of Transmission" control character. + const uint8_t $eot = 0x04; + + // "Enquiry" control character. + const uint8_t $enq = 0x05; + + // "Acknowledgment" control character. + const uint8_t $ack = 0x06; + + // "Bell" control character. + const uint8_t $bel = 0x07; + + // "Backspace" control character. + const uint8_t $bs = 0x08; + + // "Horizontal Tab" control character. + const uint8_t $tab = 0x09; + + // "Line feed" control character. + const uint8_t $lf = 0x0A; + + // "Vertical Tab" control character. + const uint8_t $vt = 0x0B; + + // "Form feed" control character. + const uint8_t $ff = 0x0C; + + // "Carriage return" control character. + const uint8_t $cr = 0x0D; + + // "Shift Out" control character. + const uint8_t $so = 0x0E; + + // "Shift In" control character. + const uint8_t $si = 0x0F; + + // "Data Link Escape" control character. + const uint8_t $dle = 0x10; + + // "Device Control 1" control character (oft. XON). + const uint8_t $dc1 = 0x11; + + // "Device Control 2" control character. + const uint8_t $dc2 = 0x12; + + // "Device Control 3" control character (oft. XOFF). + const uint8_t $dc3 = 0x13; + + // "Device Control 4" control character. + const uint8_t $dc4 = 0x14; + + // "Negative Acknowledgment" control character. + const uint8_t $nak = 0x15; + + // "Synchronous idle" control character. + const uint8_t $syn = 0x16; + + // "End of Transmission Block" control character. + const uint8_t $etb = 0x17; + + // "Cancel" control character. + const uint8_t $can = 0x18; + + // "End of Medium" control character. + const uint8_t $em = 0x19; + + // "Substitute" control character. + const uint8_t $sub = 0x1A; + + // "Escape" control character. + const uint8_t $esc = 0x1B; + + // "File Separator" control character. + const uint8_t $fs = 0x1C; + + // "Group Separator" control character. + const uint8_t $gs = 0x1D; + + // "Record Separator" control character. + const uint8_t $rs = 0x1E; + + // "Unit Separator" control character. + const uint8_t $us = 0x1F; + + // "Delete" control character. + const uint8_t $del = 0x7F; + + ///////////////////////////////////////////////////////////////////////// + // Visible characters. + ///////////////////////////////////////////////////////////////////////// + + // Space character. + const uint8_t $space = 0x20; + + // Character '!'. + const uint8_t $exclamation = 0x21; + + // Character '"'. + const uint8_t $quote = 0x22; + + // Character '#'. + const uint8_t $hash = 0x23; + + // Character '$'. + const uint8_t $dollar = 0x24; + + // Character '%'. + const uint8_t $percent = 0x25; + + // Character '&'. + const uint8_t $ampersand = 0x26; + + // Character "'". + const uint8_t $apos = 0x27; + + // Character '('. + const uint8_t $lparen = 0x28; + + // Character ')'. + const uint8_t $rparen = 0x29; + + // Character '*'. + const uint8_t $asterisk = 0x2A; + + // Character '+'. + const uint8_t $plus = 0x2B; + + // Character ','. + const uint8_t $comma = 0x2C; + + // Character '-'. + const uint8_t $minus = 0x2D; + + // Character '.'. + const uint8_t $dot = 0x2E; + + // Character '/'. + const uint8_t $slash = 0x2F; + + // Character '0'. + const uint8_t $0 = 0x30; + + // Character '1'. + const uint8_t $1 = 0x31; + + // Character '2'. + const uint8_t $2 = 0x32; + + // Character '3'. + const uint8_t $3 = 0x33; + + // Character '4'. + const uint8_t $4 = 0x34; + + // Character '5'. + const uint8_t $5 = 0x35; + + // Character '6'. + const uint8_t $6 = 0x36; + + // Character '7'. + const uint8_t $7 = 0x37; + + // Character '8'. + const uint8_t $8 = 0x38; + + // Character '9'. + const uint8_t $9 = 0x39; + + // Character ':'. + const uint8_t $colon = 0x3A; + + // Character ';'. + const uint8_t $semicolon = 0x3B; + + // Character '<'. + const uint8_t $lt = 0x3C; + + // Character '='. + const uint8_t $equal = 0x3D; + + // Character '>'. + const uint8_t $gt = 0x3E; + + // Character '?'. + const uint8_t $question = 0x3F; + + // Character '@'. + const uint8_t $at = 0x40; + + // Character 'A'. + const uint8_t $A = 0x41; + + // Character 'B'. + const uint8_t $B = 0x42; + + // Character 'C'. + const uint8_t $C = 0x43; + + // Character 'D'. + const uint8_t $D = 0x44; + + // Character 'E'. + const uint8_t $E = 0x45; + + // Character 'F'. + const uint8_t $F = 0x46; + + // Character 'G'. + const uint8_t $G = 0x47; + + // Character 'H'. + const uint8_t $H = 0x48; + + // Character 'I'. + const uint8_t $I = 0x49; + + // Character 'J'. + const uint8_t $J = 0x4A; + + // Character 'K'. + const uint8_t $K = 0x4B; + + // Character 'L'. + const uint8_t $L = 0x4C; + + // Character 'M'. + const uint8_t $M = 0x4D; + + // Character 'N'. + const uint8_t $N = 0x4E; + + // Character 'O'. + const uint8_t $O = 0x4F; + + // Character 'P'. + const uint8_t $P = 0x50; + + // Character 'Q'. + const uint8_t $Q = 0x51; + + // Character 'R'. + const uint8_t $R = 0x52; + + // Character 'S'. + const uint8_t $S = 0x53; + + // Character 'T'. + const uint8_t $T = 0x54; + + // Character 'U'. + const uint8_t $U = 0x55; + + // Character 'V'. + const uint8_t $V = 0x56; + + // Character 'W'. + const uint8_t $W = 0x57; + + // Character 'X'. + const uint8_t $X = 0x58; + + // Character 'Y'. + const uint8_t $Y = 0x59; + + // Character 'Z'. + const uint8_t $Z = 0x5A; + + // Character '['. + const uint8_t $lbracket = 0x5B; + + // Character '\'. + const uint8_t $backslash = 0x5C; + + // Character ']'. + const uint8_t $rbracket = 0x5D; + + // Character '^'. + const uint8_t $circumflex = 0x5E; + + // Character '^'. + const uint8_t $caret = 0x5E; + + // Character '^'. + const uint8_t $hat = 0x5E; + + // Character '_'. + const uint8_t $_ = 0x5F; + + // Character '_'. + const uint8_t $underscore = 0x5F; + + // Character '_'. + const uint8_t $underline = 0x5F; + + // Character '`'. + const uint8_t $backquote = 0x60; + + // Character '`'. + const uint8_t $grave = 0x60; + + // Character 'a'. + const uint8_t $a = 0x61; + + // Character 'b'. + const uint8_t $b = 0x62; + + // Character 'c'. + const uint8_t $c = 0x63; + + // Character 'd'. + const uint8_t $d = 0x64; + + // Character 'e'. + const uint8_t $e = 0x65; + + // Character 'f'. + const uint8_t $f = 0x66; + + // Character 'g'. + const uint8_t $g = 0x67; + + // Character 'h'. + const uint8_t $h = 0x68; + + // Character 'i'. + const uint8_t $i = 0x69; + + // Character 'j'. + const uint8_t $j = 0x6A; + + // Character 'k'. + const uint8_t $k = 0x6B; + + // Character 'l'. + const uint8_t $l = 0x6C; + + // Character 'm'. + const uint8_t $m = 0x6D; + + // Character 'n'. + const uint8_t $n = 0x6E; + + // Character 'o'. + const uint8_t $o = 0x6F; + + // Character 'p'. + const uint8_t $p = 0x70; + + // Character 'q'. + const uint8_t $q = 0x71; + + // Character 'r'. + const uint8_t $r = 0x72; + + // Character 's'. + const uint8_t $s = 0x73; + + // Character 't'. + const uint8_t $t = 0x74; + + // Character 'u'. + const uint8_t $u = 0x75; + + // Character 'v'. + const uint8_t $v = 0x76; + + // Character 'w'. + const uint8_t $w = 0x77; + + // Character 'x'. + const uint8_t $x = 0x78; + + // Character 'y'. + const uint8_t $y = 0x79; + + // Character 'z'. + const uint8_t $z = 0x7A; + + // Character '{'. + const uint8_t $lbrace = 0x7B; + + // Character '|'. + const uint8_t $pipe = 0x7C; + + // Character '|'. + const uint8_t $bar = 0x7C; + + // Character '}'. + const uint8_t $rbrace = 0x7D; + + // Character '~'. + const uint8_t $tilde = 0x7E; + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + } + +} + +#endif diff --git a/src/check_nesting.cpp b/src/check_nesting.cpp deleted file mode 100644 index 8e099535e5..0000000000 --- a/src/check_nesting.cpp +++ /dev/null @@ -1,393 +0,0 @@ -// sass.hpp must go before all system headers to get the -// __EXTENSIONS__ fix on Solaris. -#include "sass.hpp" -#include "ast.hpp" -#include "check_nesting.hpp" - -namespace Sass { - - CheckNesting::CheckNesting() - : parents(sass::vector()), - traces(sass::vector()), - parent(0), current_mixin_definition(0) - { } - - void error(AST_Node* node, Backtraces traces, sass::string msg) { - traces.push_back(Backtrace(node->pstate())); - throw Exception::InvalidSass(node->pstate(), traces, msg); - } - - Statement* CheckNesting::visit_children(Statement* parent) - { - Statement* old_parent = this->parent; - - if (AtRootRule* root = Cast(parent)) { - sass::vector old_parents = this->parents; - sass::vector new_parents; - - for (size_t i = 0, L = this->parents.size(); i < L; i++) { - Statement* p = this->parents.at(i); - if (!root->exclude_node(p)) { - new_parents.push_back(p); - } - } - this->parents = new_parents; - - for (size_t i = this->parents.size(); i > 0; i--) { - Statement* p = 0; - Statement* gp = 0; - if (i > 0) p = this->parents.at(i - 1); - if (i > 1) gp = this->parents.at(i - 2); - - if (!this->is_transparent_parent(p, gp)) { - this->parent = p; - break; - } - } - - AtRootRule* ar = Cast(parent); - Block* ret = ar->block(); - - if (ret != NULL) { - for (auto n : ret->elements()) { - n->perform(this); - } - } - - this->parent = old_parent; - this->parents = old_parents; - - return ret; - } - - if (!this->is_transparent_parent(parent, old_parent)) { - this->parent = parent; - } - - this->parents.push_back(parent); - - Block* b = Cast(parent); - - if (Trace* trace = Cast(parent)) { - if (trace->type() == 'i') { - this->traces.push_back(Backtrace(trace->pstate())); - } - } - - if (!b) { - if (ParentStatement* bb = Cast(parent)) { - b = bb->block(); - } - } - - if (b) { - for (auto n : b->elements()) { - n->perform(this); - } - } - - this->parent = old_parent; - this->parents.pop_back(); - - if (Trace* trace = Cast(parent)) { - if (trace->type() == 'i') { - this->traces.pop_back(); - } - } - - return b; - } - - - Statement* CheckNesting::operator()(Block* b) - { - return this->visit_children(b); - } - - Statement* CheckNesting::operator()(Definition* n) - { - if (!this->should_visit(n)) return NULL; - if (!is_mixin(n)) { - visit_children(n); - return n; - } - - Definition* old_mixin_definition = this->current_mixin_definition; - this->current_mixin_definition = n; - - visit_children(n); - - this->current_mixin_definition = old_mixin_definition; - - return n; - } - - Statement* CheckNesting::operator()(If* i) - { - this->visit_children(i); - - if (Block* b = Cast(i->alternative())) { - for (auto n : b->elements()) n->perform(this); - } - - return i; - } - - bool CheckNesting::should_visit(Statement* node) - { - if (!this->parent) return true; - - if (Cast(node)) - { this->invalid_content_parent(this->parent, node); } - - if (is_charset(node)) - { this->invalid_charset_parent(this->parent, node); } - - if (Cast(node)) - { this->invalid_extend_parent(this->parent, node); } - - // if (Cast(node)) - // { this->invalid_import_parent(this->parent); } - - if (this->is_mixin(node)) - { this->invalid_mixin_definition_parent(this->parent, node); } - - if (this->is_function(node)) - { this->invalid_function_parent(this->parent, node); } - - if (this->is_function(this->parent)) - { this->invalid_function_child(node); } - - if (Declaration* d = Cast(node)) - { - this->invalid_prop_parent(this->parent, node); - this->invalid_value_child(d->value()); - } - - if (Cast(this->parent)) - { this->invalid_prop_child(node); } - - if (Cast(node)) - { this->invalid_return_parent(this->parent, node); } - - return true; - } - - void CheckNesting::invalid_content_parent(Statement* parent, AST_Node* node) - { - if (!this->current_mixin_definition) { - error(node, traces, "@content may only be used within a mixin."); - } - } - - void CheckNesting::invalid_charset_parent(Statement* parent, AST_Node* node) - { - if (!( - is_root_node(parent) - )) { - error(node, traces, "@charset may only be used at the root of a document."); - } - } - - void CheckNesting::invalid_extend_parent(Statement* parent, AST_Node* node) - { - if (!( - Cast(parent) || - Cast(parent) || - is_mixin(parent) - )) { - error(node, traces, "Extend directives may only be used within rules."); - } - } - - // void CheckNesting::invalid_import_parent(Statement* parent, AST_Node* node) - // { - // for (auto pp : this->parents) { - // if ( - // Cast(pp) || - // Cast(pp) || - // Cast(pp) || - // Cast(pp) || - // Cast(pp) || - // Cast(pp) || - // is_mixin(pp) - // ) { - // error(node, traces, "Import directives may not be defined within control directives or other mixins."); - // } - // } - - // if (this->is_root_node(parent)) { - // return; - // } - - // if (false/*n.css_import?*/) { - // error(node, traces, "CSS import directives may only be used at the root of a document."); - // } - // } - - void CheckNesting::invalid_mixin_definition_parent(Statement* parent, AST_Node* node) - { - for (Statement* pp : this->parents) { - if ( - Cast(pp) || - Cast(pp) || - Cast(pp) || - Cast(pp) || - Cast(pp) || - Cast(pp) || - is_mixin(pp) - ) { - error(node, traces, "Mixins may not be defined within control directives or other mixins."); - } - } - } - - void CheckNesting::invalid_function_parent(Statement* parent, AST_Node* node) - { - for (Statement* pp : this->parents) { - if ( - Cast(pp) || - Cast(pp) || - Cast(pp) || - Cast(pp) || - Cast(pp) || - Cast(pp) || - is_mixin(pp) - ) { - error(node, traces, "Functions may not be defined within control directives or other mixins."); - } - } - } - - void CheckNesting::invalid_function_child(Statement* child) - { - if (!( - Cast(child) || - Cast(child) || - Cast(child) || - Cast(child) || - Cast(child) || - Cast(child) || - Cast(child) || - Cast(child) || - Cast(child) || - // Ruby Sass doesn't distinguish variables and assignments - Cast(child) || - Cast(child) || - Cast(child) - )) { - error(child, traces, "Functions can only contain variable declarations and control directives."); - } - } - - void CheckNesting::invalid_prop_child(Statement* child) - { - if (!( - Cast(child) || - Cast(child) || - Cast(child) || - Cast(child) || - Cast(child) || - Cast(child) || - Cast(child) || - Cast(child) - )) { - error(child, traces, "Illegal nesting: Only properties may be nested beneath properties."); - } - } - - void CheckNesting::invalid_prop_parent(Statement* parent, AST_Node* node) - { - if (!( - is_mixin(parent) || - is_directive_node(parent) || - Cast(parent) || - Cast(parent) || - Cast(parent) || - Cast(parent) - )) { - error(node, traces, "Properties are only allowed within rules, directives, mixin includes, or other properties."); - } - } - - void CheckNesting::invalid_value_child(AST_Node* d) - { - if (Map* m = Cast(d)) { - traces.push_back(Backtrace(m->pstate())); - throw Exception::InvalidValue(traces, *m); - } - if (Number* n = Cast(d)) { - if (!n->is_valid_css_unit()) { - traces.push_back(Backtrace(n->pstate())); - throw Exception::InvalidValue(traces, *n); - } - } - - // error(dbg + " isn't a valid CSS value.", m->pstate(),); - - } - - void CheckNesting::invalid_return_parent(Statement* parent, AST_Node* node) - { - if (!this->is_function(parent)) { - error(node, traces, "@return may only be used within a function."); - } - } - - bool CheckNesting::is_transparent_parent(Statement* parent, Statement* grandparent) - { - bool parent_bubbles = parent && parent->bubbles(); - - bool valid_bubble_node = parent_bubbles && - !is_root_node(grandparent) && - !is_at_root_node(grandparent); - - return Cast(parent) || - Cast(parent) || - Cast(parent) || - Cast(parent) || - Cast(parent) || - Cast(parent) || - valid_bubble_node; - } - - bool CheckNesting::is_charset(Statement* n) - { - AtRule* d = Cast(n); - return d && d->keyword() == "charset"; - } - - bool CheckNesting::is_mixin(Statement* n) - { - Definition* def = Cast(n); - return def && def->type() == Definition::MIXIN; - } - - bool CheckNesting::is_function(Statement* n) - { - Definition* def = Cast(n); - return def && def->type() == Definition::FUNCTION; - } - - bool CheckNesting::is_root_node(Statement* n) - { - if (Cast(n)) return false; - - Block* b = Cast(n); - return b && b->is_root(); - } - - bool CheckNesting::is_at_root_node(Statement* n) - { - return Cast(n) != NULL; - } - - bool CheckNesting::is_directive_node(Statement* n) - { - return Cast(n) || - Cast(n) || - Cast(n) || - Cast(n) || - Cast(n); - } -} diff --git a/src/check_nesting.hpp b/src/check_nesting.hpp deleted file mode 100644 index 48bd99c6e4..0000000000 --- a/src/check_nesting.hpp +++ /dev/null @@ -1,70 +0,0 @@ -#ifndef SASS_CHECK_NESTING_H -#define SASS_CHECK_NESTING_H - -// sass.hpp must go before all system headers to get the -// __EXTENSIONS__ fix on Solaris. -#include "sass.hpp" -#include "ast.hpp" -#include "operation.hpp" -#include - -namespace Sass { - - class CheckNesting : public Operation_CRTP { - - sass::vector parents; - Backtraces traces; - Statement* parent; - Definition* current_mixin_definition; - - Statement* before(Statement*); - Statement* visit_children(Statement*); - - public: - CheckNesting(); - ~CheckNesting() { } - - Statement* operator()(Block*); - Statement* operator()(Definition*); - Statement* operator()(If*); - - template - Statement* fallback(U x) { - Statement* s = Cast(x); - if (s && this->should_visit(s)) { - Block* b1 = Cast(s); - ParentStatement* b2 = Cast(s); - if (b1 || b2) return visit_children(s); - } - return s; - } - - private: - void invalid_content_parent(Statement*, AST_Node*); - void invalid_charset_parent(Statement*, AST_Node*); - void invalid_extend_parent(Statement*, AST_Node*); - // void invalid_import_parent(Statement*); - void invalid_mixin_definition_parent(Statement*, AST_Node*); - void invalid_function_parent(Statement*, AST_Node*); - - void invalid_function_child(Statement*); - void invalid_prop_child(Statement*); - void invalid_prop_parent(Statement*, AST_Node*); - void invalid_return_parent(Statement*, AST_Node*); - void invalid_value_child(AST_Node*); - - bool is_transparent_parent(Statement*, Statement*); - - bool should_visit(Statement*); - - bool is_charset(Statement*); - bool is_mixin(Statement*); - bool is_function(Statement*); - bool is_root_node(Statement*); - bool is_at_root_node(Statement*); - bool is_directive_node(Statement*); - }; - -} - -#endif diff --git a/src/color_maps.cpp b/src/color_maps.cpp index 15dba2ebf5..ffa90bea82 100644 --- a/src/color_maps.cpp +++ b/src/color_maps.cpp @@ -1,652 +1,669 @@ -// sass.hpp must go before all system headers to get the -// __EXTENSIONS__ fix on Solaris. -#include "sass.hpp" - -#include "ast.hpp" +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ #include "color_maps.hpp" -#include "util_string.hpp" -namespace Sass { +#include "string_utils.hpp" + +namespace Sass +{ + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// namespace ColorNames - { - const char aliceblue [] = "aliceblue"; - const char antiquewhite [] = "antiquewhite"; - const char cyan [] = "cyan"; - const char aqua [] = "aqua"; - const char aquamarine [] = "aquamarine"; - const char azure [] = "azure"; - const char beige [] = "beige"; - const char bisque [] = "bisque"; - const char black [] = "black"; - const char blanchedalmond [] = "blanchedalmond"; - const char blue [] = "blue"; - const char blueviolet [] = "blueviolet"; - const char brown [] = "brown"; - const char burlywood [] = "burlywood"; - const char cadetblue [] = "cadetblue"; - const char chartreuse [] = "chartreuse"; - const char chocolate [] = "chocolate"; - const char coral [] = "coral"; - const char cornflowerblue [] = "cornflowerblue"; - const char cornsilk [] = "cornsilk"; - const char crimson [] = "crimson"; - const char darkblue [] = "darkblue"; - const char darkcyan [] = "darkcyan"; - const char darkgoldenrod [] = "darkgoldenrod"; - const char darkgray [] = "darkgray"; - const char darkgrey [] = "darkgrey"; - const char darkgreen [] = "darkgreen"; - const char darkkhaki [] = "darkkhaki"; - const char darkmagenta [] = "darkmagenta"; - const char darkolivegreen [] = "darkolivegreen"; - const char darkorange [] = "darkorange"; - const char darkorchid [] = "darkorchid"; - const char darkred [] = "darkred"; - const char darksalmon [] = "darksalmon"; - const char darkseagreen [] = "darkseagreen"; - const char darkslateblue [] = "darkslateblue"; - const char darkslategray [] = "darkslategray"; - const char darkslategrey [] = "darkslategrey"; - const char darkturquoise [] = "darkturquoise"; - const char darkviolet [] = "darkviolet"; - const char deeppink [] = "deeppink"; - const char deepskyblue [] = "deepskyblue"; - const char dimgray [] = "dimgray"; - const char dimgrey [] = "dimgrey"; - const char dodgerblue [] = "dodgerblue"; - const char firebrick [] = "firebrick"; - const char floralwhite [] = "floralwhite"; - const char forestgreen [] = "forestgreen"; - const char magenta [] = "magenta"; - const char fuchsia [] = "fuchsia"; - const char gainsboro [] = "gainsboro"; - const char ghostwhite [] = "ghostwhite"; - const char gold [] = "gold"; - const char goldenrod [] = "goldenrod"; - const char gray [] = "gray"; - const char grey [] = "grey"; - const char green [] = "green"; - const char greenyellow [] = "greenyellow"; - const char honeydew [] = "honeydew"; - const char hotpink [] = "hotpink"; - const char indianred [] = "indianred"; - const char indigo [] = "indigo"; - const char ivory [] = "ivory"; - const char khaki [] = "khaki"; - const char lavender [] = "lavender"; - const char lavenderblush [] = "lavenderblush"; - const char lawngreen [] = "lawngreen"; - const char lemonchiffon [] = "lemonchiffon"; - const char lightblue [] = "lightblue"; - const char lightcoral [] = "lightcoral"; - const char lightcyan [] = "lightcyan"; - const char lightgoldenrodyellow [] = "lightgoldenrodyellow"; - const char lightgray [] = "lightgray"; - const char lightgrey [] = "lightgrey"; - const char lightgreen [] = "lightgreen"; - const char lightpink [] = "lightpink"; - const char lightsalmon [] = "lightsalmon"; - const char lightseagreen [] = "lightseagreen"; - const char lightskyblue [] = "lightskyblue"; - const char lightslategray [] = "lightslategray"; - const char lightslategrey [] = "lightslategrey"; - const char lightsteelblue [] = "lightsteelblue"; - const char lightyellow [] = "lightyellow"; - const char lime [] = "lime"; - const char limegreen [] = "limegreen"; - const char linen [] = "linen"; - const char maroon [] = "maroon"; - const char mediumaquamarine [] = "mediumaquamarine"; - const char mediumblue [] = "mediumblue"; - const char mediumorchid [] = "mediumorchid"; - const char mediumpurple [] = "mediumpurple"; - const char mediumseagreen [] = "mediumseagreen"; - const char mediumslateblue [] = "mediumslateblue"; - const char mediumspringgreen [] = "mediumspringgreen"; - const char mediumturquoise [] = "mediumturquoise"; - const char mediumvioletred [] = "mediumvioletred"; - const char midnightblue [] = "midnightblue"; - const char mintcream [] = "mintcream"; - const char mistyrose [] = "mistyrose"; - const char moccasin [] = "moccasin"; - const char navajowhite [] = "navajowhite"; - const char navy [] = "navy"; - const char oldlace [] = "oldlace"; - const char olive [] = "olive"; - const char olivedrab [] = "olivedrab"; - const char orange [] = "orange"; - const char orangered [] = "orangered"; - const char orchid [] = "orchid"; - const char palegoldenrod [] = "palegoldenrod"; - const char palegreen [] = "palegreen"; - const char paleturquoise [] = "paleturquoise"; - const char palevioletred [] = "palevioletred"; - const char papayawhip [] = "papayawhip"; - const char peachpuff [] = "peachpuff"; - const char peru [] = "peru"; - const char pink [] = "pink"; - const char plum [] = "plum"; - const char powderblue [] = "powderblue"; - const char purple [] = "purple"; - const char red [] = "red"; - const char rosybrown [] = "rosybrown"; - const char royalblue [] = "royalblue"; - const char saddlebrown [] = "saddlebrown"; - const char salmon [] = "salmon"; - const char sandybrown [] = "sandybrown"; - const char seagreen [] = "seagreen"; - const char seashell [] = "seashell"; - const char sienna [] = "sienna"; - const char silver [] = "silver"; - const char skyblue [] = "skyblue"; - const char slateblue [] = "slateblue"; - const char slategray [] = "slategray"; - const char slategrey [] = "slategrey"; - const char snow [] = "snow"; - const char springgreen [] = "springgreen"; - const char steelblue [] = "steelblue"; - const char tan [] = "tan"; - const char teal [] = "teal"; - const char thistle [] = "thistle"; - const char tomato [] = "tomato"; - const char turquoise [] = "turquoise"; - const char violet [] = "violet"; - const char wheat [] = "wheat"; - const char white [] = "white"; - const char whitesmoke [] = "whitesmoke"; - const char yellow [] = "yellow"; - const char yellowgreen [] = "yellowgreen"; - const char rebeccapurple [] = "rebeccapurple"; - const char transparent [] = "transparent"; - } + { + const char aliceblue[] = "aliceblue"; + const char antiquewhite[] = "antiquewhite"; + const char cyan[] = "cyan"; + const char aqua[] = "aqua"; + const char aquamarine[] = "aquamarine"; + const char azure[] = "azure"; + const char beige[] = "beige"; + const char bisque[] = "bisque"; + const char black[] = "black"; + const char blanchedalmond[] = "blanchedalmond"; + const char blue[] = "blue"; + const char blueviolet[] = "blueviolet"; + const char brown[] = "brown"; + const char burlywood[] = "burlywood"; + const char cadetblue[] = "cadetblue"; + const char chartreuse[] = "chartreuse"; + const char chocolate[] = "chocolate"; + const char coral[] = "coral"; + const char cornflowerblue[] = "cornflowerblue"; + const char cornsilk[] = "cornsilk"; + const char crimson[] = "crimson"; + const char darkblue[] = "darkblue"; + const char darkcyan[] = "darkcyan"; + const char darkgoldenrod[] = "darkgoldenrod"; + const char darkgray[] = "darkgray"; + const char darkgrey[] = "darkgrey"; + const char darkgreen[] = "darkgreen"; + const char darkkhaki[] = "darkkhaki"; + const char darkmagenta[] = "darkmagenta"; + const char darkolivegreen[] = "darkolivegreen"; + const char darkorange[] = "darkorange"; + const char darkorchid[] = "darkorchid"; + const char darkred[] = "darkred"; + const char darksalmon[] = "darksalmon"; + const char darkseagreen[] = "darkseagreen"; + const char darkslateblue[] = "darkslateblue"; + const char darkslategray[] = "darkslategray"; + const char darkslategrey[] = "darkslategrey"; + const char darkturquoise[] = "darkturquoise"; + const char darkviolet[] = "darkviolet"; + const char deeppink[] = "deeppink"; + const char deepskyblue[] = "deepskyblue"; + const char dimgray[] = "dimgray"; + const char dimgrey[] = "dimgrey"; + const char dodgerblue[] = "dodgerblue"; + const char firebrick[] = "firebrick"; + const char floralwhite[] = "floralwhite"; + const char forestgreen[] = "forestgreen"; + const char magenta[] = "magenta"; + const char fuchsia[] = "fuchsia"; + const char gainsboro[] = "gainsboro"; + const char ghostwhite[] = "ghostwhite"; + const char gold[] = "gold"; + const char goldenrod[] = "goldenrod"; + const char gray[] = "gray"; + const char grey[] = "grey"; + const char green[] = "green"; + const char greenyellow[] = "greenyellow"; + const char honeydew[] = "honeydew"; + const char hotpink[] = "hotpink"; + const char indianred[] = "indianred"; + const char indigo[] = "indigo"; + const char ivory[] = "ivory"; + const char khaki[] = "khaki"; + const char lavender[] = "lavender"; + const char lavenderblush[] = "lavenderblush"; + const char lawngreen[] = "lawngreen"; + const char lemonchiffon[] = "lemonchiffon"; + const char lightblue[] = "lightblue"; + const char lightcoral[] = "lightcoral"; + const char lightcyan[] = "lightcyan"; + const char lightgoldenrodyellow[] = "lightgoldenrodyellow"; + const char lightgray[] = "lightgray"; + const char lightgrey[] = "lightgrey"; + const char lightgreen[] = "lightgreen"; + const char lightpink[] = "lightpink"; + const char lightsalmon[] = "lightsalmon"; + const char lightseagreen[] = "lightseagreen"; + const char lightskyblue[] = "lightskyblue"; + const char lightslategray[] = "lightslategray"; + const char lightslategrey[] = "lightslategrey"; + const char lightsteelblue[] = "lightsteelblue"; + const char lightyellow[] = "lightyellow"; + const char lime[] = "lime"; + const char limegreen[] = "limegreen"; + const char linen[] = "linen"; + const char maroon[] = "maroon"; + const char mediumaquamarine[] = "mediumaquamarine"; + const char mediumblue[] = "mediumblue"; + const char mediumorchid[] = "mediumorchid"; + const char mediumpurple[] = "mediumpurple"; + const char mediumseagreen[] = "mediumseagreen"; + const char mediumslateblue[] = "mediumslateblue"; + const char mediumspringgreen[] = "mediumspringgreen"; + const char mediumturquoise[] = "mediumturquoise"; + const char mediumvioletred[] = "mediumvioletred"; + const char midnightblue[] = "midnightblue"; + const char mintcream[] = "mintcream"; + const char mistyrose[] = "mistyrose"; + const char moccasin[] = "moccasin"; + const char navajowhite[] = "navajowhite"; + const char navy[] = "navy"; + const char oldlace[] = "oldlace"; + const char olive[] = "olive"; + const char olivedrab[] = "olivedrab"; + const char orange[] = "orange"; + const char orangered[] = "orangered"; + const char orchid[] = "orchid"; + const char palegoldenrod[] = "palegoldenrod"; + const char palegreen[] = "palegreen"; + const char paleturquoise[] = "paleturquoise"; + const char palevioletred[] = "palevioletred"; + const char papayawhip[] = "papayawhip"; + const char peachpuff[] = "peachpuff"; + const char peru[] = "peru"; + const char pink[] = "pink"; + const char plum[] = "plum"; + const char powderblue[] = "powderblue"; + const char purple[] = "purple"; + const char red[] = "red"; + const char rosybrown[] = "rosybrown"; + const char royalblue[] = "royalblue"; + const char saddlebrown[] = "saddlebrown"; + const char salmon[] = "salmon"; + const char sandybrown[] = "sandybrown"; + const char seagreen[] = "seagreen"; + const char seashell[] = "seashell"; + const char sienna[] = "sienna"; + const char silver[] = "silver"; + const char skyblue[] = "skyblue"; + const char slateblue[] = "slateblue"; + const char slategray[] = "slategray"; + const char slategrey[] = "slategrey"; + const char snow[] = "snow"; + const char springgreen[] = "springgreen"; + const char steelblue[] = "steelblue"; + const char tan[] = "tan"; + const char teal[] = "teal"; + const char thistle[] = "thistle"; + const char tomato[] = "tomato"; + const char turquoise[] = "turquoise"; + const char violet[] = "violet"; + const char wheat[] = "wheat"; + const char white[] = "white"; + const char whitesmoke[] = "whitesmoke"; + const char yellow[] = "yellow"; + const char yellowgreen[] = "yellowgreen"; + const char rebeccapurple[] = "rebeccapurple"; + const char transparent[] = "transparent"; + } // namespace ColorNames + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + namespace Colors + { + const SourceSpan color_table(SourceSpan::internal("[COLOR TABLE]")); + const ColorRgba aliceblue(color_table, 240, 248, 255, 1); + const ColorRgba antiquewhite(color_table, 250, 235, 215, 1); + const ColorRgba cyan(color_table, 0, 255, 255, 1); + const ColorRgba aqua(color_table, 0, 255, 255, 1); + const ColorRgba aquamarine(color_table, 127, 255, 212, 1); + const ColorRgba azure(color_table, 240, 255, 255, 1); + const ColorRgba beige(color_table, 245, 245, 220, 1); + const ColorRgba bisque(color_table, 255, 228, 196, 1); + const ColorRgba black(color_table, 0, 0, 0, 1); + const ColorRgba blanchedalmond(color_table, 255, 235, 205, 1); + const ColorRgba blue(color_table, 0, 0, 255, 1); + const ColorRgba blueviolet(color_table, 138, 43, 226, 1); + const ColorRgba brown(color_table, 165, 42, 42, 1); + const ColorRgba burlywood(color_table, 222, 184, 135, 1); + const ColorRgba cadetblue(color_table, 95, 158, 160, 1); + const ColorRgba chartreuse(color_table, 127, 255, 0, 1); + const ColorRgba chocolate(color_table, 210, 105, 30, 1); + const ColorRgba coral(color_table, 255, 127, 80, 1); + const ColorRgba cornflowerblue(color_table, 100, 149, 237, 1); + const ColorRgba cornsilk(color_table, 255, 248, 220, 1); + const ColorRgba crimson(color_table, 220, 20, 60, 1); + const ColorRgba darkblue(color_table, 0, 0, 139, 1); + const ColorRgba darkcyan(color_table, 0, 139, 139, 1); + const ColorRgba darkgoldenrod(color_table, 184, 134, 11, 1); + const ColorRgba darkgray(color_table, 169, 169, 169, 1); + const ColorRgba darkgrey(color_table, 169, 169, 169, 1); + const ColorRgba darkgreen(color_table, 0, 100, 0, 1); + const ColorRgba darkkhaki(color_table, 189, 183, 107, 1); + const ColorRgba darkmagenta(color_table, 139, 0, 139, 1); + const ColorRgba darkolivegreen(color_table, 85, 107, 47, 1); + const ColorRgba darkorange(color_table, 255, 140, 0, 1); + const ColorRgba darkorchid(color_table, 153, 50, 204, 1); + const ColorRgba darkred(color_table, 139, 0, 0, 1); + const ColorRgba darksalmon(color_table, 233, 150, 122, 1); + const ColorRgba darkseagreen(color_table, 143, 188, 143, 1); + const ColorRgba darkslateblue(color_table, 72, 61, 139, 1); + const ColorRgba darkslategray(color_table, 47, 79, 79, 1); + const ColorRgba darkslategrey(color_table, 47, 79, 79, 1); + const ColorRgba darkturquoise(color_table, 0, 206, 209, 1); + const ColorRgba darkviolet(color_table, 148, 0, 211, 1); + const ColorRgba deeppink(color_table, 255, 20, 147, 1); + const ColorRgba deepskyblue(color_table, 0, 191, 255, 1); + const ColorRgba dimgray(color_table, 105, 105, 105, 1); + const ColorRgba dimgrey(color_table, 105, 105, 105, 1); + const ColorRgba dodgerblue(color_table, 30, 144, 255, 1); + const ColorRgba firebrick(color_table, 178, 34, 34, 1); + const ColorRgba floralwhite(color_table, 255, 250, 240, 1); + const ColorRgba forestgreen(color_table, 34, 139, 34, 1); + const ColorRgba magenta(color_table, 255, 0, 255, 1); + const ColorRgba fuchsia(color_table, 255, 0, 255, 1); + const ColorRgba gainsboro(color_table, 220, 220, 220, 1); + const ColorRgba ghostwhite(color_table, 248, 248, 255, 1); + const ColorRgba gold(color_table, 255, 215, 0, 1); + const ColorRgba goldenrod(color_table, 218, 165, 32, 1); + const ColorRgba gray(color_table, 128, 128, 128, 1); + const ColorRgba grey(color_table, 128, 128, 128, 1); + const ColorRgba green(color_table, 0, 128, 0, 1); + const ColorRgba greenyellow(color_table, 173, 255, 47, 1); + const ColorRgba honeydew(color_table, 240, 255, 240, 1); + const ColorRgba hotpink(color_table, 255, 105, 180, 1); + const ColorRgba indianred(color_table, 205, 92, 92, 1); + const ColorRgba indigo(color_table, 75, 0, 130, 1); + const ColorRgba ivory(color_table, 255, 255, 240, 1); + const ColorRgba khaki(color_table, 240, 230, 140, 1); + const ColorRgba lavender(color_table, 230, 230, 250, 1); + const ColorRgba lavenderblush(color_table, 255, 240, 245, 1); + const ColorRgba lawngreen(color_table, 124, 252, 0, 1); + const ColorRgba lemonchiffon(color_table, 255, 250, 205, 1); + const ColorRgba lightblue(color_table, 173, 216, 230, 1); + const ColorRgba lightcoral(color_table, 240, 128, 128, 1); + const ColorRgba lightcyan(color_table, 224, 255, 255, 1); + const ColorRgba lightgoldenrodyellow(color_table, 250, 250, 210, 1); + const ColorRgba lightgray(color_table, 211, 211, 211, 1); + const ColorRgba lightgrey(color_table, 211, 211, 211, 1); + const ColorRgba lightgreen(color_table, 144, 238, 144, 1); + const ColorRgba lightpink(color_table, 255, 182, 193, 1); + const ColorRgba lightsalmon(color_table, 255, 160, 122, 1); + const ColorRgba lightseagreen(color_table, 32, 178, 170, 1); + const ColorRgba lightskyblue(color_table, 135, 206, 250, 1); + const ColorRgba lightslategray(color_table, 119, 136, 153, 1); + const ColorRgba lightslategrey(color_table, 119, 136, 153, 1); + const ColorRgba lightsteelblue(color_table, 176, 196, 222, 1); + const ColorRgba lightyellow(color_table, 255, 255, 224, 1); + const ColorRgba lime(color_table, 0, 255, 0, 1); + const ColorRgba limegreen(color_table, 50, 205, 50, 1); + const ColorRgba linen(color_table, 250, 240, 230, 1); + const ColorRgba maroon(color_table, 128, 0, 0, 1); + const ColorRgba mediumaquamarine(color_table, 102, 205, 170, 1); + const ColorRgba mediumblue(color_table, 0, 0, 205, 1); + const ColorRgba mediumorchid(color_table, 186, 85, 211, 1); + const ColorRgba mediumpurple(color_table, 147, 112, 219, 1); + const ColorRgba mediumseagreen(color_table, 60, 179, 113, 1); + const ColorRgba mediumslateblue(color_table, 123, 104, 238, 1); + const ColorRgba mediumspringgreen(color_table, 0, 250, 154, 1); + const ColorRgba mediumturquoise(color_table, 72, 209, 204, 1); + const ColorRgba mediumvioletred(color_table, 199, 21, 133, 1); + const ColorRgba midnightblue(color_table, 25, 25, 112, 1); + const ColorRgba mintcream(color_table, 245, 255, 250, 1); + const ColorRgba mistyrose(color_table, 255, 228, 225, 1); + const ColorRgba moccasin(color_table, 255, 228, 181, 1); + const ColorRgba navajowhite(color_table, 255, 222, 173, 1); + const ColorRgba navy(color_table, 0, 0, 128, 1); + const ColorRgba oldlace(color_table, 253, 245, 230, 1); + const ColorRgba olive(color_table, 128, 128, 0, 1); + const ColorRgba olivedrab(color_table, 107, 142, 35, 1); + const ColorRgba orange(color_table, 255, 165, 0, 1); + const ColorRgba orangered(color_table, 255, 69, 0, 1); + const ColorRgba orchid(color_table, 218, 112, 214, 1); + const ColorRgba palegoldenrod(color_table, 238, 232, 170, 1); + const ColorRgba palegreen(color_table, 152, 251, 152, 1); + const ColorRgba paleturquoise(color_table, 175, 238, 238, 1); + const ColorRgba palevioletred(color_table, 219, 112, 147, 1); + const ColorRgba papayawhip(color_table, 255, 239, 213, 1); + const ColorRgba peachpuff(color_table, 255, 218, 185, 1); + const ColorRgba peru(color_table, 205, 133, 63, 1); + const ColorRgba pink(color_table, 255, 192, 203, 1); + const ColorRgba plum(color_table, 221, 160, 221, 1); + const ColorRgba powderblue(color_table, 176, 224, 230, 1); + const ColorRgba purple(color_table, 128, 0, 128, 1); + const ColorRgba red(color_table, 255, 0, 0, 1); + const ColorRgba rosybrown(color_table, 188, 143, 143, 1); + const ColorRgba royalblue(color_table, 65, 105, 225, 1); + const ColorRgba saddlebrown(color_table, 139, 69, 19, 1); + const ColorRgba salmon(color_table, 250, 128, 114, 1); + const ColorRgba sandybrown(color_table, 244, 164, 96, 1); + const ColorRgba seagreen(color_table, 46, 139, 87, 1); + const ColorRgba seashell(color_table, 255, 245, 238, 1); + const ColorRgba sienna(color_table, 160, 82, 45, 1); + const ColorRgba silver(color_table, 192, 192, 192, 1); + const ColorRgba skyblue(color_table, 135, 206, 235, 1); + const ColorRgba slateblue(color_table, 106, 90, 205, 1); + const ColorRgba slategray(color_table, 112, 128, 144, 1); + const ColorRgba slategrey(color_table, 112, 128, 144, 1); + const ColorRgba snow(color_table, 255, 250, 250, 1); + const ColorRgba springgreen(color_table, 0, 255, 127, 1); + const ColorRgba steelblue(color_table, 70, 130, 180, 1); + const ColorRgba tan(color_table, 210, 180, 140, 1); + const ColorRgba teal(color_table, 0, 128, 128, 1); + const ColorRgba thistle(color_table, 216, 191, 216, 1); + const ColorRgba tomato(color_table, 255, 99, 71, 1); + const ColorRgba turquoise(color_table, 64, 224, 208, 1); + const ColorRgba violet(color_table, 238, 130, 238, 1); + const ColorRgba wheat(color_table, 245, 222, 179, 1); + const ColorRgba white(color_table, 255, 255, 255, 1); + const ColorRgba whitesmoke(color_table, 245, 245, 245, 1); + const ColorRgba yellow(color_table, 255, 255, 0, 1); + const ColorRgba yellowgreen(color_table, 154, 205, 50, 1); + const ColorRgba rebeccapurple(color_table, 102, 51, 153, 1); + const ColorRgba transparent(color_table, 0, 0, 0, 0); + } // namespace Colors + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + static const auto* const colors_to_names = new std::unordered_map{ + {240 * 0x10000 + 248 * 0x100 + 255, ColorNames::aliceblue}, + {250 * 0x10000 + 235 * 0x100 + 215, ColorNames::antiquewhite}, + {0 * 0x10000 + 255 * 0x100 + 255, ColorNames::cyan}, + {127 * 0x10000 + 255 * 0x100 + 212, ColorNames::aquamarine}, + {240 * 0x10000 + 255 * 0x100 + 255, ColorNames::azure}, + {245 * 0x10000 + 245 * 0x100 + 220, ColorNames::beige}, + {255 * 0x10000 + 228 * 0x100 + 196, ColorNames::bisque}, + {0 * 0x10000 + 0 * 0x100 + 0, ColorNames::black}, + {255 * 0x10000 + 235 * 0x100 + 205, ColorNames::blanchedalmond}, + {0 * 0x10000 + 0 * 0x100 + 255, ColorNames::blue}, + {138 * 0x10000 + 43 * 0x100 + 226, ColorNames::blueviolet}, + {165 * 0x10000 + 42 * 0x100 + 42, ColorNames::brown}, + {222 * 0x10000 + 184 * 0x100 + 135, ColorNames::burlywood}, + {95 * 0x10000 + 158 * 0x100 + 160, ColorNames::cadetblue}, + {127 * 0x10000 + 255 * 0x100 + 0, ColorNames::chartreuse}, + {210 * 0x10000 + 105 * 0x100 + 30, ColorNames::chocolate}, + {255 * 0x10000 + 127 * 0x100 + 80, ColorNames::coral}, + {100 * 0x10000 + 149 * 0x100 + 237, ColorNames::cornflowerblue}, + {255 * 0x10000 + 248 * 0x100 + 220, ColorNames::cornsilk}, + {220 * 0x10000 + 20 * 0x100 + 60, ColorNames::crimson}, + {0 * 0x10000 + 0 * 0x100 + 139, ColorNames::darkblue}, + {0 * 0x10000 + 139 * 0x100 + 139, ColorNames::darkcyan}, + {184 * 0x10000 + 134 * 0x100 + 11, ColorNames::darkgoldenrod}, + {169 * 0x10000 + 169 * 0x100 + 169, ColorNames::darkgray}, + {0 * 0x10000 + 100 * 0x100 + 0, ColorNames::darkgreen}, + {189 * 0x10000 + 183 * 0x100 + 107, ColorNames::darkkhaki}, + {139 * 0x10000 + 0 * 0x100 + 139, ColorNames::darkmagenta}, + {85 * 0x10000 + 107 * 0x100 + 47, ColorNames::darkolivegreen}, + {255 * 0x10000 + 140 * 0x100 + 0, ColorNames::darkorange}, + {153 * 0x10000 + 50 * 0x100 + 204, ColorNames::darkorchid}, + {139 * 0x10000 + 0 * 0x100 + 0, ColorNames::darkred}, + {233 * 0x10000 + 150 * 0x100 + 122, ColorNames::darksalmon}, + {143 * 0x10000 + 188 * 0x100 + 143, ColorNames::darkseagreen}, + {72 * 0x10000 + 61 * 0x100 + 139, ColorNames::darkslateblue}, + {47 * 0x10000 + 79 * 0x100 + 79, ColorNames::darkslategray}, + {0 * 0x10000 + 206 * 0x100 + 209, ColorNames::darkturquoise}, + {148 * 0x10000 + 0 * 0x100 + 211, ColorNames::darkviolet}, + {255 * 0x10000 + 20 * 0x100 + 147, ColorNames::deeppink}, + {0 * 0x10000 + 191 * 0x100 + 255, ColorNames::deepskyblue}, + {105 * 0x10000 + 105 * 0x100 + 105, ColorNames::dimgray}, + {30 * 0x10000 + 144 * 0x100 + 255, ColorNames::dodgerblue}, + {178 * 0x10000 + 34 * 0x100 + 34, ColorNames::firebrick}, + {255 * 0x10000 + 250 * 0x100 + 240, ColorNames::floralwhite}, + {34 * 0x10000 + 139 * 0x100 + 34, ColorNames::forestgreen}, + {255 * 0x10000 + 0 * 0x100 + 255, ColorNames::magenta}, + {220 * 0x10000 + 220 * 0x100 + 220, ColorNames::gainsboro}, + {248 * 0x10000 + 248 * 0x100 + 255, ColorNames::ghostwhite}, + {255 * 0x10000 + 215 * 0x100 + 0, ColorNames::gold}, + {218 * 0x10000 + 165 * 0x100 + 32, ColorNames::goldenrod}, + {128 * 0x10000 + 128 * 0x100 + 128, ColorNames::gray}, + {0 * 0x10000 + 128 * 0x100 + 0, ColorNames::green}, + {173 * 0x10000 + 255 * 0x100 + 47, ColorNames::greenyellow}, + {240 * 0x10000 + 255 * 0x100 + 240, ColorNames::honeydew}, + {255 * 0x10000 + 105 * 0x100 + 180, ColorNames::hotpink}, + {205 * 0x10000 + 92 * 0x100 + 92, ColorNames::indianred}, + {75 * 0x10000 + 0 * 0x100 + 130, ColorNames::indigo}, + {255 * 0x10000 + 255 * 0x100 + 240, ColorNames::ivory}, + {240 * 0x10000 + 230 * 0x100 + 140, ColorNames::khaki}, + {230 * 0x10000 + 230 * 0x100 + 250, ColorNames::lavender}, + {255 * 0x10000 + 240 * 0x100 + 245, ColorNames::lavenderblush}, + {124 * 0x10000 + 252 * 0x100 + 0, ColorNames::lawngreen}, + {255 * 0x10000 + 250 * 0x100 + 205, ColorNames::lemonchiffon}, + {173 * 0x10000 + 216 * 0x100 + 230, ColorNames::lightblue}, + {240 * 0x10000 + 128 * 0x100 + 128, ColorNames::lightcoral}, + {224 * 0x10000 + 255 * 0x100 + 255, ColorNames::lightcyan}, + {250 * 0x10000 + 250 * 0x100 + 210, ColorNames::lightgoldenrodyellow}, + {211 * 0x10000 + 211 * 0x100 + 211, ColorNames::lightgray}, + {144 * 0x10000 + 238 * 0x100 + 144, ColorNames::lightgreen}, + {255 * 0x10000 + 182 * 0x100 + 193, ColorNames::lightpink}, + {255 * 0x10000 + 160 * 0x100 + 122, ColorNames::lightsalmon}, + {32 * 0x10000 + 178 * 0x100 + 170, ColorNames::lightseagreen}, + {135 * 0x10000 + 206 * 0x100 + 250, ColorNames::lightskyblue}, + {119 * 0x10000 + 136 * 0x100 + 153, ColorNames::lightslategray}, + {176 * 0x10000 + 196 * 0x100 + 222, ColorNames::lightsteelblue}, + {255 * 0x10000 + 255 * 0x100 + 224, ColorNames::lightyellow}, + {0 * 0x10000 + 255 * 0x100 + 0, ColorNames::lime}, + {50 * 0x10000 + 205 * 0x100 + 50, ColorNames::limegreen}, + {250 * 0x10000 + 240 * 0x100 + 230, ColorNames::linen}, + {128 * 0x10000 + 0 * 0x100 + 0, ColorNames::maroon}, + {102 * 0x10000 + 205 * 0x100 + 170, ColorNames::mediumaquamarine}, + {0 * 0x10000 + 0 * 0x100 + 205, ColorNames::mediumblue}, + {186 * 0x10000 + 85 * 0x100 + 211, ColorNames::mediumorchid}, + {147 * 0x10000 + 112 * 0x100 + 219, ColorNames::mediumpurple}, + {60 * 0x10000 + 179 * 0x100 + 113, ColorNames::mediumseagreen}, + {123 * 0x10000 + 104 * 0x100 + 238, ColorNames::mediumslateblue}, + {0 * 0x10000 + 250 * 0x100 + 154, ColorNames::mediumspringgreen}, + {72 * 0x10000 + 209 * 0x100 + 204, ColorNames::mediumturquoise}, + {199 * 0x10000 + 21 * 0x100 + 133, ColorNames::mediumvioletred}, + {25 * 0x10000 + 25 * 0x100 + 112, ColorNames::midnightblue}, + {245 * 0x10000 + 255 * 0x100 + 250, ColorNames::mintcream}, + {255 * 0x10000 + 228 * 0x100 + 225, ColorNames::mistyrose}, + {255 * 0x10000 + 228 * 0x100 + 181, ColorNames::moccasin}, + {255 * 0x10000 + 222 * 0x100 + 173, ColorNames::navajowhite}, + {0 * 0x10000 + 0 * 0x100 + 128, ColorNames::navy}, + {253 * 0x10000 + 245 * 0x100 + 230, ColorNames::oldlace}, + {128 * 0x10000 + 128 * 0x100 + 0, ColorNames::olive}, + {107 * 0x10000 + 142 * 0x100 + 35, ColorNames::olivedrab}, + {255 * 0x10000 + 165 * 0x100 + 0, ColorNames::orange}, + {255 * 0x10000 + 69 * 0x100 + 0, ColorNames::orangered}, + {218 * 0x10000 + 112 * 0x100 + 214, ColorNames::orchid}, + {238 * 0x10000 + 232 * 0x100 + 170, ColorNames::palegoldenrod}, + {152 * 0x10000 + 251 * 0x100 + 152, ColorNames::palegreen}, + {175 * 0x10000 + 238 * 0x100 + 238, ColorNames::paleturquoise}, + {219 * 0x10000 + 112 * 0x100 + 147, ColorNames::palevioletred}, + {255 * 0x10000 + 239 * 0x100 + 213, ColorNames::papayawhip}, + {255 * 0x10000 + 218 * 0x100 + 185, ColorNames::peachpuff}, + {205 * 0x10000 + 133 * 0x100 + 63, ColorNames::peru}, + {255 * 0x10000 + 192 * 0x100 + 203, ColorNames::pink}, + {221 * 0x10000 + 160 * 0x100 + 221, ColorNames::plum}, + {176 * 0x10000 + 224 * 0x100 + 230, ColorNames::powderblue}, + {128 * 0x10000 + 0 * 0x100 + 128, ColorNames::purple}, + {255 * 0x10000 + 0 * 0x100 + 0, ColorNames::red}, + {188 * 0x10000 + 143 * 0x100 + 143, ColorNames::rosybrown}, + {65 * 0x10000 + 105 * 0x100 + 225, ColorNames::royalblue}, + {139 * 0x10000 + 69 * 0x100 + 19, ColorNames::saddlebrown}, + {250 * 0x10000 + 128 * 0x100 + 114, ColorNames::salmon}, + {244 * 0x10000 + 164 * 0x100 + 96, ColorNames::sandybrown}, + {46 * 0x10000 + 139 * 0x100 + 87, ColorNames::seagreen}, + {255 * 0x10000 + 245 * 0x100 + 238, ColorNames::seashell}, + {160 * 0x10000 + 82 * 0x100 + 45, ColorNames::sienna}, + {192 * 0x10000 + 192 * 0x100 + 192, ColorNames::silver}, + {135 * 0x10000 + 206 * 0x100 + 235, ColorNames::skyblue}, + {106 * 0x10000 + 90 * 0x100 + 205, ColorNames::slateblue}, + {112 * 0x10000 + 128 * 0x100 + 144, ColorNames::slategray}, + {255 * 0x10000 + 250 * 0x100 + 250, ColorNames::snow}, + {0 * 0x10000 + 255 * 0x100 + 127, ColorNames::springgreen}, + {70 * 0x10000 + 130 * 0x100 + 180, ColorNames::steelblue}, + {210 * 0x10000 + 180 * 0x100 + 140, ColorNames::tan}, + {0 * 0x10000 + 128 * 0x100 + 128, ColorNames::teal}, + {216 * 0x10000 + 191 * 0x100 + 216, ColorNames::thistle}, + {255 * 0x10000 + 99 * 0x100 + 71, ColorNames::tomato}, + {64 * 0x10000 + 224 * 0x100 + 208, ColorNames::turquoise}, + {238 * 0x10000 + 130 * 0x100 + 238, ColorNames::violet}, + {245 * 0x10000 + 222 * 0x100 + 179, ColorNames::wheat}, + {255 * 0x10000 + 255 * 0x100 + 255, ColorNames::white}, + {245 * 0x10000 + 245 * 0x100 + 245, ColorNames::whitesmoke}, + {255 * 0x10000 + 255 * 0x100 + 0, ColorNames::yellow}, + {154 * 0x10000 + 205 * 0x100 + 50, ColorNames::yellowgreen}, + {102 * 0x10000 + 51 * 0x100 + 153, ColorNames::rebeccapurple}}; - namespace Colors { - const SourceSpan color_table("[COLOR TABLE]"); - const Color_RGBA aliceblue(color_table, 240, 248, 255, 1); - const Color_RGBA antiquewhite(color_table, 250, 235, 215, 1); - const Color_RGBA cyan(color_table, 0, 255, 255, 1); - const Color_RGBA aqua(color_table, 0, 255, 255, 1); - const Color_RGBA aquamarine(color_table, 127, 255, 212, 1); - const Color_RGBA azure(color_table, 240, 255, 255, 1); - const Color_RGBA beige(color_table, 245, 245, 220, 1); - const Color_RGBA bisque(color_table, 255, 228, 196, 1); - const Color_RGBA black(color_table, 0, 0, 0, 1); - const Color_RGBA blanchedalmond(color_table, 255, 235, 205, 1); - const Color_RGBA blue(color_table, 0, 0, 255, 1); - const Color_RGBA blueviolet(color_table, 138, 43, 226, 1); - const Color_RGBA brown(color_table, 165, 42, 42, 1); - const Color_RGBA burlywood(color_table, 222, 184, 135, 1); - const Color_RGBA cadetblue(color_table, 95, 158, 160, 1); - const Color_RGBA chartreuse(color_table, 127, 255, 0, 1); - const Color_RGBA chocolate(color_table, 210, 105, 30, 1); - const Color_RGBA coral(color_table, 255, 127, 80, 1); - const Color_RGBA cornflowerblue(color_table, 100, 149, 237, 1); - const Color_RGBA cornsilk(color_table, 255, 248, 220, 1); - const Color_RGBA crimson(color_table, 220, 20, 60, 1); - const Color_RGBA darkblue(color_table, 0, 0, 139, 1); - const Color_RGBA darkcyan(color_table, 0, 139, 139, 1); - const Color_RGBA darkgoldenrod(color_table, 184, 134, 11, 1); - const Color_RGBA darkgray(color_table, 169, 169, 169, 1); - const Color_RGBA darkgrey(color_table, 169, 169, 169, 1); - const Color_RGBA darkgreen(color_table, 0, 100, 0, 1); - const Color_RGBA darkkhaki(color_table, 189, 183, 107, 1); - const Color_RGBA darkmagenta(color_table, 139, 0, 139, 1); - const Color_RGBA darkolivegreen(color_table, 85, 107, 47, 1); - const Color_RGBA darkorange(color_table, 255, 140, 0, 1); - const Color_RGBA darkorchid(color_table, 153, 50, 204, 1); - const Color_RGBA darkred(color_table, 139, 0, 0, 1); - const Color_RGBA darksalmon(color_table, 233, 150, 122, 1); - const Color_RGBA darkseagreen(color_table, 143, 188, 143, 1); - const Color_RGBA darkslateblue(color_table, 72, 61, 139, 1); - const Color_RGBA darkslategray(color_table, 47, 79, 79, 1); - const Color_RGBA darkslategrey(color_table, 47, 79, 79, 1); - const Color_RGBA darkturquoise(color_table, 0, 206, 209, 1); - const Color_RGBA darkviolet(color_table, 148, 0, 211, 1); - const Color_RGBA deeppink(color_table, 255, 20, 147, 1); - const Color_RGBA deepskyblue(color_table, 0, 191, 255, 1); - const Color_RGBA dimgray(color_table, 105, 105, 105, 1); - const Color_RGBA dimgrey(color_table, 105, 105, 105, 1); - const Color_RGBA dodgerblue(color_table, 30, 144, 255, 1); - const Color_RGBA firebrick(color_table, 178, 34, 34, 1); - const Color_RGBA floralwhite(color_table, 255, 250, 240, 1); - const Color_RGBA forestgreen(color_table, 34, 139, 34, 1); - const Color_RGBA magenta(color_table, 255, 0, 255, 1); - const Color_RGBA fuchsia(color_table, 255, 0, 255, 1); - const Color_RGBA gainsboro(color_table, 220, 220, 220, 1); - const Color_RGBA ghostwhite(color_table, 248, 248, 255, 1); - const Color_RGBA gold(color_table, 255, 215, 0, 1); - const Color_RGBA goldenrod(color_table, 218, 165, 32, 1); - const Color_RGBA gray(color_table, 128, 128, 128, 1); - const Color_RGBA grey(color_table, 128, 128, 128, 1); - const Color_RGBA green(color_table, 0, 128, 0, 1); - const Color_RGBA greenyellow(color_table, 173, 255, 47, 1); - const Color_RGBA honeydew(color_table, 240, 255, 240, 1); - const Color_RGBA hotpink(color_table, 255, 105, 180, 1); - const Color_RGBA indianred(color_table, 205, 92, 92, 1); - const Color_RGBA indigo(color_table, 75, 0, 130, 1); - const Color_RGBA ivory(color_table, 255, 255, 240, 1); - const Color_RGBA khaki(color_table, 240, 230, 140, 1); - const Color_RGBA lavender(color_table, 230, 230, 250, 1); - const Color_RGBA lavenderblush(color_table, 255, 240, 245, 1); - const Color_RGBA lawngreen(color_table, 124, 252, 0, 1); - const Color_RGBA lemonchiffon(color_table, 255, 250, 205, 1); - const Color_RGBA lightblue(color_table, 173, 216, 230, 1); - const Color_RGBA lightcoral(color_table, 240, 128, 128, 1); - const Color_RGBA lightcyan(color_table, 224, 255, 255, 1); - const Color_RGBA lightgoldenrodyellow(color_table, 250, 250, 210, 1); - const Color_RGBA lightgray(color_table, 211, 211, 211, 1); - const Color_RGBA lightgrey(color_table, 211, 211, 211, 1); - const Color_RGBA lightgreen(color_table, 144, 238, 144, 1); - const Color_RGBA lightpink(color_table, 255, 182, 193, 1); - const Color_RGBA lightsalmon(color_table, 255, 160, 122, 1); - const Color_RGBA lightseagreen(color_table, 32, 178, 170, 1); - const Color_RGBA lightskyblue(color_table, 135, 206, 250, 1); - const Color_RGBA lightslategray(color_table, 119, 136, 153, 1); - const Color_RGBA lightslategrey(color_table, 119, 136, 153, 1); - const Color_RGBA lightsteelblue(color_table, 176, 196, 222, 1); - const Color_RGBA lightyellow(color_table, 255, 255, 224, 1); - const Color_RGBA lime(color_table, 0, 255, 0, 1); - const Color_RGBA limegreen(color_table, 50, 205, 50, 1); - const Color_RGBA linen(color_table, 250, 240, 230, 1); - const Color_RGBA maroon(color_table, 128, 0, 0, 1); - const Color_RGBA mediumaquamarine(color_table, 102, 205, 170, 1); - const Color_RGBA mediumblue(color_table, 0, 0, 205, 1); - const Color_RGBA mediumorchid(color_table, 186, 85, 211, 1); - const Color_RGBA mediumpurple(color_table, 147, 112, 219, 1); - const Color_RGBA mediumseagreen(color_table, 60, 179, 113, 1); - const Color_RGBA mediumslateblue(color_table, 123, 104, 238, 1); - const Color_RGBA mediumspringgreen(color_table, 0, 250, 154, 1); - const Color_RGBA mediumturquoise(color_table, 72, 209, 204, 1); - const Color_RGBA mediumvioletred(color_table, 199, 21, 133, 1); - const Color_RGBA midnightblue(color_table, 25, 25, 112, 1); - const Color_RGBA mintcream(color_table, 245, 255, 250, 1); - const Color_RGBA mistyrose(color_table, 255, 228, 225, 1); - const Color_RGBA moccasin(color_table, 255, 228, 181, 1); - const Color_RGBA navajowhite(color_table, 255, 222, 173, 1); - const Color_RGBA navy(color_table, 0, 0, 128, 1); - const Color_RGBA oldlace(color_table, 253, 245, 230, 1); - const Color_RGBA olive(color_table, 128, 128, 0, 1); - const Color_RGBA olivedrab(color_table, 107, 142, 35, 1); - const Color_RGBA orange(color_table, 255, 165, 0, 1); - const Color_RGBA orangered(color_table, 255, 69, 0, 1); - const Color_RGBA orchid(color_table, 218, 112, 214, 1); - const Color_RGBA palegoldenrod(color_table, 238, 232, 170, 1); - const Color_RGBA palegreen(color_table, 152, 251, 152, 1); - const Color_RGBA paleturquoise(color_table, 175, 238, 238, 1); - const Color_RGBA palevioletred(color_table, 219, 112, 147, 1); - const Color_RGBA papayawhip(color_table, 255, 239, 213, 1); - const Color_RGBA peachpuff(color_table, 255, 218, 185, 1); - const Color_RGBA peru(color_table, 205, 133, 63, 1); - const Color_RGBA pink(color_table, 255, 192, 203, 1); - const Color_RGBA plum(color_table, 221, 160, 221, 1); - const Color_RGBA powderblue(color_table, 176, 224, 230, 1); - const Color_RGBA purple(color_table, 128, 0, 128, 1); - const Color_RGBA red(color_table, 255, 0, 0, 1); - const Color_RGBA rosybrown(color_table, 188, 143, 143, 1); - const Color_RGBA royalblue(color_table, 65, 105, 225, 1); - const Color_RGBA saddlebrown(color_table, 139, 69, 19, 1); - const Color_RGBA salmon(color_table, 250, 128, 114, 1); - const Color_RGBA sandybrown(color_table, 244, 164, 96, 1); - const Color_RGBA seagreen(color_table, 46, 139, 87, 1); - const Color_RGBA seashell(color_table, 255, 245, 238, 1); - const Color_RGBA sienna(color_table, 160, 82, 45, 1); - const Color_RGBA silver(color_table, 192, 192, 192, 1); - const Color_RGBA skyblue(color_table, 135, 206, 235, 1); - const Color_RGBA slateblue(color_table, 106, 90, 205, 1); - const Color_RGBA slategray(color_table, 112, 128, 144, 1); - const Color_RGBA slategrey(color_table, 112, 128, 144, 1); - const Color_RGBA snow(color_table, 255, 250, 250, 1); - const Color_RGBA springgreen(color_table, 0, 255, 127, 1); - const Color_RGBA steelblue(color_table, 70, 130, 180, 1); - const Color_RGBA tan(color_table, 210, 180, 140, 1); - const Color_RGBA teal(color_table, 0, 128, 128, 1); - const Color_RGBA thistle(color_table, 216, 191, 216, 1); - const Color_RGBA tomato(color_table, 255, 99, 71, 1); - const Color_RGBA turquoise(color_table, 64, 224, 208, 1); - const Color_RGBA violet(color_table, 238, 130, 238, 1); - const Color_RGBA wheat(color_table, 245, 222, 179, 1); - const Color_RGBA white(color_table, 255, 255, 255, 1); - const Color_RGBA whitesmoke(color_table, 245, 245, 245, 1); - const Color_RGBA yellow(color_table, 255, 255, 0, 1); - const Color_RGBA yellowgreen(color_table, 154, 205, 50, 1); - const Color_RGBA rebeccapurple(color_table, 102, 51, 153, 1); - const Color_RGBA transparent(color_table, 0, 0, 0, 0); - } + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// - static const auto* const colors_to_names = new std::unordered_map { - { 240 * 0x10000 + 248 * 0x100 + 255, ColorNames::aliceblue }, - { 250 * 0x10000 + 235 * 0x100 + 215, ColorNames::antiquewhite }, - { 0 * 0x10000 + 255 * 0x100 + 255, ColorNames::cyan }, - { 127 * 0x10000 + 255 * 0x100 + 212, ColorNames::aquamarine }, - { 240 * 0x10000 + 255 * 0x100 + 255, ColorNames::azure }, - { 245 * 0x10000 + 245 * 0x100 + 220, ColorNames::beige }, - { 255 * 0x10000 + 228 * 0x100 + 196, ColorNames::bisque }, - { 0 * 0x10000 + 0 * 0x100 + 0, ColorNames::black }, - { 255 * 0x10000 + 235 * 0x100 + 205, ColorNames::blanchedalmond }, - { 0 * 0x10000 + 0 * 0x100 + 255, ColorNames::blue }, - { 138 * 0x10000 + 43 * 0x100 + 226, ColorNames::blueviolet }, - { 165 * 0x10000 + 42 * 0x100 + 42, ColorNames::brown }, - { 222 * 0x10000 + 184 * 0x100 + 135, ColorNames::burlywood }, - { 95 * 0x10000 + 158 * 0x100 + 160, ColorNames::cadetblue }, - { 127 * 0x10000 + 255 * 0x100 + 0, ColorNames::chartreuse }, - { 210 * 0x10000 + 105 * 0x100 + 30, ColorNames::chocolate }, - { 255 * 0x10000 + 127 * 0x100 + 80, ColorNames::coral }, - { 100 * 0x10000 + 149 * 0x100 + 237, ColorNames::cornflowerblue }, - { 255 * 0x10000 + 248 * 0x100 + 220, ColorNames::cornsilk }, - { 220 * 0x10000 + 20 * 0x100 + 60, ColorNames::crimson }, - { 0 * 0x10000 + 0 * 0x100 + 139, ColorNames::darkblue }, - { 0 * 0x10000 + 139 * 0x100 + 139, ColorNames::darkcyan }, - { 184 * 0x10000 + 134 * 0x100 + 11, ColorNames::darkgoldenrod }, - { 169 * 0x10000 + 169 * 0x100 + 169, ColorNames::darkgray }, - { 0 * 0x10000 + 100 * 0x100 + 0, ColorNames::darkgreen }, - { 189 * 0x10000 + 183 * 0x100 + 107, ColorNames::darkkhaki }, - { 139 * 0x10000 + 0 * 0x100 + 139, ColorNames::darkmagenta }, - { 85 * 0x10000 + 107 * 0x100 + 47, ColorNames::darkolivegreen }, - { 255 * 0x10000 + 140 * 0x100 + 0, ColorNames::darkorange }, - { 153 * 0x10000 + 50 * 0x100 + 204, ColorNames::darkorchid }, - { 139 * 0x10000 + 0 * 0x100 + 0, ColorNames::darkred }, - { 233 * 0x10000 + 150 * 0x100 + 122, ColorNames::darksalmon }, - { 143 * 0x10000 + 188 * 0x100 + 143, ColorNames::darkseagreen }, - { 72 * 0x10000 + 61 * 0x100 + 139, ColorNames::darkslateblue }, - { 47 * 0x10000 + 79 * 0x100 + 79, ColorNames::darkslategray }, - { 0 * 0x10000 + 206 * 0x100 + 209, ColorNames::darkturquoise }, - { 148 * 0x10000 + 0 * 0x100 + 211, ColorNames::darkviolet }, - { 255 * 0x10000 + 20 * 0x100 + 147, ColorNames::deeppink }, - { 0 * 0x10000 + 191 * 0x100 + 255, ColorNames::deepskyblue }, - { 105 * 0x10000 + 105 * 0x100 + 105, ColorNames::dimgray }, - { 30 * 0x10000 + 144 * 0x100 + 255, ColorNames::dodgerblue }, - { 178 * 0x10000 + 34 * 0x100 + 34, ColorNames::firebrick }, - { 255 * 0x10000 + 250 * 0x100 + 240, ColorNames::floralwhite }, - { 34 * 0x10000 + 139 * 0x100 + 34, ColorNames::forestgreen }, - { 255 * 0x10000 + 0 * 0x100 + 255, ColorNames::magenta }, - { 220 * 0x10000 + 220 * 0x100 + 220, ColorNames::gainsboro }, - { 248 * 0x10000 + 248 * 0x100 + 255, ColorNames::ghostwhite }, - { 255 * 0x10000 + 215 * 0x100 + 0, ColorNames::gold }, - { 218 * 0x10000 + 165 * 0x100 + 32, ColorNames::goldenrod }, - { 128 * 0x10000 + 128 * 0x100 + 128, ColorNames::gray }, - { 0 * 0x10000 + 128 * 0x100 + 0, ColorNames::green }, - { 173 * 0x10000 + 255 * 0x100 + 47, ColorNames::greenyellow }, - { 240 * 0x10000 + 255 * 0x100 + 240, ColorNames::honeydew }, - { 255 * 0x10000 + 105 * 0x100 + 180, ColorNames::hotpink }, - { 205 * 0x10000 + 92 * 0x100 + 92, ColorNames::indianred }, - { 75 * 0x10000 + 0 * 0x100 + 130, ColorNames::indigo }, - { 255 * 0x10000 + 255 * 0x100 + 240, ColorNames::ivory }, - { 240 * 0x10000 + 230 * 0x100 + 140, ColorNames::khaki }, - { 230 * 0x10000 + 230 * 0x100 + 250, ColorNames::lavender }, - { 255 * 0x10000 + 240 * 0x100 + 245, ColorNames::lavenderblush }, - { 124 * 0x10000 + 252 * 0x100 + 0, ColorNames::lawngreen }, - { 255 * 0x10000 + 250 * 0x100 + 205, ColorNames::lemonchiffon }, - { 173 * 0x10000 + 216 * 0x100 + 230, ColorNames::lightblue }, - { 240 * 0x10000 + 128 * 0x100 + 128, ColorNames::lightcoral }, - { 224 * 0x10000 + 255 * 0x100 + 255, ColorNames::lightcyan }, - { 250 * 0x10000 + 250 * 0x100 + 210, ColorNames::lightgoldenrodyellow }, - { 211 * 0x10000 + 211 * 0x100 + 211, ColorNames::lightgray }, - { 144 * 0x10000 + 238 * 0x100 + 144, ColorNames::lightgreen }, - { 255 * 0x10000 + 182 * 0x100 + 193, ColorNames::lightpink }, - { 255 * 0x10000 + 160 * 0x100 + 122, ColorNames::lightsalmon }, - { 32 * 0x10000 + 178 * 0x100 + 170, ColorNames::lightseagreen }, - { 135 * 0x10000 + 206 * 0x100 + 250, ColorNames::lightskyblue }, - { 119 * 0x10000 + 136 * 0x100 + 153, ColorNames::lightslategray }, - { 176 * 0x10000 + 196 * 0x100 + 222, ColorNames::lightsteelblue }, - { 255 * 0x10000 + 255 * 0x100 + 224, ColorNames::lightyellow }, - { 0 * 0x10000 + 255 * 0x100 + 0, ColorNames::lime }, - { 50 * 0x10000 + 205 * 0x100 + 50, ColorNames::limegreen }, - { 250 * 0x10000 + 240 * 0x100 + 230, ColorNames::linen }, - { 128 * 0x10000 + 0 * 0x100 + 0, ColorNames::maroon }, - { 102 * 0x10000 + 205 * 0x100 + 170, ColorNames::mediumaquamarine }, - { 0 * 0x10000 + 0 * 0x100 + 205, ColorNames::mediumblue }, - { 186 * 0x10000 + 85 * 0x100 + 211, ColorNames::mediumorchid }, - { 147 * 0x10000 + 112 * 0x100 + 219, ColorNames::mediumpurple }, - { 60 * 0x10000 + 179 * 0x100 + 113, ColorNames::mediumseagreen }, - { 123 * 0x10000 + 104 * 0x100 + 238, ColorNames::mediumslateblue }, - { 0 * 0x10000 + 250 * 0x100 + 154, ColorNames::mediumspringgreen }, - { 72 * 0x10000 + 209 * 0x100 + 204, ColorNames::mediumturquoise }, - { 199 * 0x10000 + 21 * 0x100 + 133, ColorNames::mediumvioletred }, - { 25 * 0x10000 + 25 * 0x100 + 112, ColorNames::midnightblue }, - { 245 * 0x10000 + 255 * 0x100 + 250, ColorNames::mintcream }, - { 255 * 0x10000 + 228 * 0x100 + 225, ColorNames::mistyrose }, - { 255 * 0x10000 + 228 * 0x100 + 181, ColorNames::moccasin }, - { 255 * 0x10000 + 222 * 0x100 + 173, ColorNames::navajowhite }, - { 0 * 0x10000 + 0 * 0x100 + 128, ColorNames::navy }, - { 253 * 0x10000 + 245 * 0x100 + 230, ColorNames::oldlace }, - { 128 * 0x10000 + 128 * 0x100 + 0, ColorNames::olive }, - { 107 * 0x10000 + 142 * 0x100 + 35, ColorNames::olivedrab }, - { 255 * 0x10000 + 165 * 0x100 + 0, ColorNames::orange }, - { 255 * 0x10000 + 69 * 0x100 + 0, ColorNames::orangered }, - { 218 * 0x10000 + 112 * 0x100 + 214, ColorNames::orchid }, - { 238 * 0x10000 + 232 * 0x100 + 170, ColorNames::palegoldenrod }, - { 152 * 0x10000 + 251 * 0x100 + 152, ColorNames::palegreen }, - { 175 * 0x10000 + 238 * 0x100 + 238, ColorNames::paleturquoise }, - { 219 * 0x10000 + 112 * 0x100 + 147, ColorNames::palevioletred }, - { 255 * 0x10000 + 239 * 0x100 + 213, ColorNames::papayawhip }, - { 255 * 0x10000 + 218 * 0x100 + 185, ColorNames::peachpuff }, - { 205 * 0x10000 + 133 * 0x100 + 63, ColorNames::peru }, - { 255 * 0x10000 + 192 * 0x100 + 203, ColorNames::pink }, - { 221 * 0x10000 + 160 * 0x100 + 221, ColorNames::plum }, - { 176 * 0x10000 + 224 * 0x100 + 230, ColorNames::powderblue }, - { 128 * 0x10000 + 0 * 0x100 + 128, ColorNames::purple }, - { 255 * 0x10000 + 0 * 0x100 + 0, ColorNames::red }, - { 188 * 0x10000 + 143 * 0x100 + 143, ColorNames::rosybrown }, - { 65 * 0x10000 + 105 * 0x100 + 225, ColorNames::royalblue }, - { 139 * 0x10000 + 69 * 0x100 + 19, ColorNames::saddlebrown }, - { 250 * 0x10000 + 128 * 0x100 + 114, ColorNames::salmon }, - { 244 * 0x10000 + 164 * 0x100 + 96, ColorNames::sandybrown }, - { 46 * 0x10000 + 139 * 0x100 + 87, ColorNames::seagreen }, - { 255 * 0x10000 + 245 * 0x100 + 238, ColorNames::seashell }, - { 160 * 0x10000 + 82 * 0x100 + 45, ColorNames::sienna }, - { 192 * 0x10000 + 192 * 0x100 + 192, ColorNames::silver }, - { 135 * 0x10000 + 206 * 0x100 + 235, ColorNames::skyblue }, - { 106 * 0x10000 + 90 * 0x100 + 205, ColorNames::slateblue }, - { 112 * 0x10000 + 128 * 0x100 + 144, ColorNames::slategray }, - { 255 * 0x10000 + 250 * 0x100 + 250, ColorNames::snow }, - { 0 * 0x10000 + 255 * 0x100 + 127, ColorNames::springgreen }, - { 70 * 0x10000 + 130 * 0x100 + 180, ColorNames::steelblue }, - { 210 * 0x10000 + 180 * 0x100 + 140, ColorNames::tan }, - { 0 * 0x10000 + 128 * 0x100 + 128, ColorNames::teal }, - { 216 * 0x10000 + 191 * 0x100 + 216, ColorNames::thistle }, - { 255 * 0x10000 + 99 * 0x100 + 71, ColorNames::tomato }, - { 64 * 0x10000 + 224 * 0x100 + 208, ColorNames::turquoise }, - { 238 * 0x10000 + 130 * 0x100 + 238, ColorNames::violet }, - { 245 * 0x10000 + 222 * 0x100 + 179, ColorNames::wheat }, - { 255 * 0x10000 + 255 * 0x100 + 255, ColorNames::white }, - { 245 * 0x10000 + 245 * 0x100 + 245, ColorNames::whitesmoke }, - { 255 * 0x10000 + 255 * 0x100 + 0, ColorNames::yellow }, - { 154 * 0x10000 + 205 * 0x100 + 50, ColorNames::yellowgreen }, - { 102 * 0x10000 + 51 * 0x100 + 153, ColorNames::rebeccapurple } - }; + static const auto* const names_to_colors = + new std::unordered_map{ + {ColorNames::aliceblue, &Colors::aliceblue}, + {ColorNames::antiquewhite, &Colors::antiquewhite}, + {ColorNames::cyan, &Colors::cyan}, + {ColorNames::aqua, &Colors::aqua}, + {ColorNames::aquamarine, &Colors::aquamarine}, + {ColorNames::azure, &Colors::azure}, + {ColorNames::beige, &Colors::beige}, + {ColorNames::bisque, &Colors::bisque}, + {ColorNames::black, &Colors::black}, + {ColorNames::blanchedalmond, &Colors::blanchedalmond}, + {ColorNames::blue, &Colors::blue}, + {ColorNames::blueviolet, &Colors::blueviolet}, + {ColorNames::brown, &Colors::brown}, + {ColorNames::burlywood, &Colors::burlywood}, + {ColorNames::cadetblue, &Colors::cadetblue}, + {ColorNames::chartreuse, &Colors::chartreuse}, + {ColorNames::chocolate, &Colors::chocolate}, + {ColorNames::coral, &Colors::coral}, + {ColorNames::cornflowerblue, &Colors::cornflowerblue}, + {ColorNames::cornsilk, &Colors::cornsilk}, + {ColorNames::crimson, &Colors::crimson}, + {ColorNames::darkblue, &Colors::darkblue}, + {ColorNames::darkcyan, &Colors::darkcyan}, + {ColorNames::darkgoldenrod, &Colors::darkgoldenrod}, + {ColorNames::darkgray, &Colors::darkgray}, + {ColorNames::darkgrey, &Colors::darkgrey}, + {ColorNames::darkgreen, &Colors::darkgreen}, + {ColorNames::darkkhaki, &Colors::darkkhaki}, + {ColorNames::darkmagenta, &Colors::darkmagenta}, + {ColorNames::darkolivegreen, &Colors::darkolivegreen}, + {ColorNames::darkorange, &Colors::darkorange}, + {ColorNames::darkorchid, &Colors::darkorchid}, + {ColorNames::darkred, &Colors::darkred}, + {ColorNames::darksalmon, &Colors::darksalmon}, + {ColorNames::darkseagreen, &Colors::darkseagreen}, + {ColorNames::darkslateblue, &Colors::darkslateblue}, + {ColorNames::darkslategray, &Colors::darkslategray}, + {ColorNames::darkslategrey, &Colors::darkslategrey}, + {ColorNames::darkturquoise, &Colors::darkturquoise}, + {ColorNames::darkviolet, &Colors::darkviolet}, + {ColorNames::deeppink, &Colors::deeppink}, + {ColorNames::deepskyblue, &Colors::deepskyblue}, + {ColorNames::dimgray, &Colors::dimgray}, + {ColorNames::dimgrey, &Colors::dimgrey}, + {ColorNames::dodgerblue, &Colors::dodgerblue}, + {ColorNames::firebrick, &Colors::firebrick}, + {ColorNames::floralwhite, &Colors::floralwhite}, + {ColorNames::forestgreen, &Colors::forestgreen}, + {ColorNames::magenta, &Colors::magenta}, + {ColorNames::fuchsia, &Colors::fuchsia}, + {ColorNames::gainsboro, &Colors::gainsboro}, + {ColorNames::ghostwhite, &Colors::ghostwhite}, + {ColorNames::gold, &Colors::gold}, + {ColorNames::goldenrod, &Colors::goldenrod}, + {ColorNames::gray, &Colors::gray}, + {ColorNames::grey, &Colors::grey}, + {ColorNames::green, &Colors::green}, + {ColorNames::greenyellow, &Colors::greenyellow}, + {ColorNames::honeydew, &Colors::honeydew}, + {ColorNames::hotpink, &Colors::hotpink}, + {ColorNames::indianred, &Colors::indianred}, + {ColorNames::indigo, &Colors::indigo}, + {ColorNames::ivory, &Colors::ivory}, + {ColorNames::khaki, &Colors::khaki}, + {ColorNames::lavender, &Colors::lavender}, + {ColorNames::lavenderblush, &Colors::lavenderblush}, + {ColorNames::lawngreen, &Colors::lawngreen}, + {ColorNames::lemonchiffon, &Colors::lemonchiffon}, + {ColorNames::lightblue, &Colors::lightblue}, + {ColorNames::lightcoral, &Colors::lightcoral}, + {ColorNames::lightcyan, &Colors::lightcyan}, + {ColorNames::lightgoldenrodyellow, &Colors::lightgoldenrodyellow}, + {ColorNames::lightgray, &Colors::lightgray}, + {ColorNames::lightgrey, &Colors::lightgrey}, + {ColorNames::lightgreen, &Colors::lightgreen}, + {ColorNames::lightpink, &Colors::lightpink}, + {ColorNames::lightsalmon, &Colors::lightsalmon}, + {ColorNames::lightseagreen, &Colors::lightseagreen}, + {ColorNames::lightskyblue, &Colors::lightskyblue}, + {ColorNames::lightslategray, &Colors::lightslategray}, + {ColorNames::lightslategrey, &Colors::lightslategrey}, + {ColorNames::lightsteelblue, &Colors::lightsteelblue}, + {ColorNames::lightyellow, &Colors::lightyellow}, + {ColorNames::lime, &Colors::lime}, + {ColorNames::limegreen, &Colors::limegreen}, + {ColorNames::linen, &Colors::linen}, + {ColorNames::maroon, &Colors::maroon}, + {ColorNames::mediumaquamarine, &Colors::mediumaquamarine}, + {ColorNames::mediumblue, &Colors::mediumblue}, + {ColorNames::mediumorchid, &Colors::mediumorchid}, + {ColorNames::mediumpurple, &Colors::mediumpurple}, + {ColorNames::mediumseagreen, &Colors::mediumseagreen}, + {ColorNames::mediumslateblue, &Colors::mediumslateblue}, + {ColorNames::mediumspringgreen, &Colors::mediumspringgreen}, + {ColorNames::mediumturquoise, &Colors::mediumturquoise}, + {ColorNames::mediumvioletred, &Colors::mediumvioletred}, + {ColorNames::midnightblue, &Colors::midnightblue}, + {ColorNames::mintcream, &Colors::mintcream}, + {ColorNames::mistyrose, &Colors::mistyrose}, + {ColorNames::moccasin, &Colors::moccasin}, + {ColorNames::navajowhite, &Colors::navajowhite}, + {ColorNames::navy, &Colors::navy}, + {ColorNames::oldlace, &Colors::oldlace}, + {ColorNames::olive, &Colors::olive}, + {ColorNames::olivedrab, &Colors::olivedrab}, + {ColorNames::orange, &Colors::orange}, + {ColorNames::orangered, &Colors::orangered}, + {ColorNames::orchid, &Colors::orchid}, + {ColorNames::palegoldenrod, &Colors::palegoldenrod}, + {ColorNames::palegreen, &Colors::palegreen}, + {ColorNames::paleturquoise, &Colors::paleturquoise}, + {ColorNames::palevioletred, &Colors::palevioletred}, + {ColorNames::papayawhip, &Colors::papayawhip}, + {ColorNames::peachpuff, &Colors::peachpuff}, + {ColorNames::peru, &Colors::peru}, + {ColorNames::pink, &Colors::pink}, + {ColorNames::plum, &Colors::plum}, + {ColorNames::powderblue, &Colors::powderblue}, + {ColorNames::purple, &Colors::purple}, + {ColorNames::red, &Colors::red}, + {ColorNames::rosybrown, &Colors::rosybrown}, + {ColorNames::royalblue, &Colors::royalblue}, + {ColorNames::saddlebrown, &Colors::saddlebrown}, + {ColorNames::salmon, &Colors::salmon}, + {ColorNames::sandybrown, &Colors::sandybrown}, + {ColorNames::seagreen, &Colors::seagreen}, + {ColorNames::seashell, &Colors::seashell}, + {ColorNames::sienna, &Colors::sienna}, + {ColorNames::silver, &Colors::silver}, + {ColorNames::skyblue, &Colors::skyblue}, + {ColorNames::slateblue, &Colors::slateblue}, + {ColorNames::slategray, &Colors::slategray}, + {ColorNames::slategrey, &Colors::slategrey}, + {ColorNames::snow, &Colors::snow}, + {ColorNames::springgreen, &Colors::springgreen}, + {ColorNames::steelblue, &Colors::steelblue}, + {ColorNames::tan, &Colors::tan}, + {ColorNames::teal, &Colors::teal}, + {ColorNames::thistle, &Colors::thistle}, + {ColorNames::tomato, &Colors::tomato}, + {ColorNames::turquoise, &Colors::turquoise}, + {ColorNames::violet, &Colors::violet}, + {ColorNames::wheat, &Colors::wheat}, + {ColorNames::white, &Colors::white}, + {ColorNames::whitesmoke, &Colors::whitesmoke}, + {ColorNames::yellow, &Colors::yellow}, + {ColorNames::yellowgreen, &Colors::yellowgreen}, + {ColorNames::rebeccapurple, &Colors::rebeccapurple}, + {ColorNames::transparent, &Colors::transparent}}; - static const auto *const names_to_colors = new std::unordered_map - { - { ColorNames::aliceblue, &Colors::aliceblue }, - { ColorNames::antiquewhite, &Colors::antiquewhite }, - { ColorNames::cyan, &Colors::cyan }, - { ColorNames::aqua, &Colors::aqua }, - { ColorNames::aquamarine, &Colors::aquamarine }, - { ColorNames::azure, &Colors::azure }, - { ColorNames::beige, &Colors::beige }, - { ColorNames::bisque, &Colors::bisque }, - { ColorNames::black, &Colors::black }, - { ColorNames::blanchedalmond, &Colors::blanchedalmond }, - { ColorNames::blue, &Colors::blue }, - { ColorNames::blueviolet, &Colors::blueviolet }, - { ColorNames::brown, &Colors::brown }, - { ColorNames::burlywood, &Colors::burlywood }, - { ColorNames::cadetblue, &Colors::cadetblue }, - { ColorNames::chartreuse, &Colors::chartreuse }, - { ColorNames::chocolate, &Colors::chocolate }, - { ColorNames::coral, &Colors::coral }, - { ColorNames::cornflowerblue, &Colors::cornflowerblue }, - { ColorNames::cornsilk, &Colors::cornsilk }, - { ColorNames::crimson, &Colors::crimson }, - { ColorNames::darkblue, &Colors::darkblue }, - { ColorNames::darkcyan, &Colors::darkcyan }, - { ColorNames::darkgoldenrod, &Colors::darkgoldenrod }, - { ColorNames::darkgray, &Colors::darkgray }, - { ColorNames::darkgrey, &Colors::darkgrey }, - { ColorNames::darkgreen, &Colors::darkgreen }, - { ColorNames::darkkhaki, &Colors::darkkhaki }, - { ColorNames::darkmagenta, &Colors::darkmagenta }, - { ColorNames::darkolivegreen, &Colors::darkolivegreen }, - { ColorNames::darkorange, &Colors::darkorange }, - { ColorNames::darkorchid, &Colors::darkorchid }, - { ColorNames::darkred, &Colors::darkred }, - { ColorNames::darksalmon, &Colors::darksalmon }, - { ColorNames::darkseagreen, &Colors::darkseagreen }, - { ColorNames::darkslateblue, &Colors::darkslateblue }, - { ColorNames::darkslategray, &Colors::darkslategray }, - { ColorNames::darkslategrey, &Colors::darkslategrey }, - { ColorNames::darkturquoise, &Colors::darkturquoise }, - { ColorNames::darkviolet, &Colors::darkviolet }, - { ColorNames::deeppink, &Colors::deeppink }, - { ColorNames::deepskyblue, &Colors::deepskyblue }, - { ColorNames::dimgray, &Colors::dimgray }, - { ColorNames::dimgrey, &Colors::dimgrey }, - { ColorNames::dodgerblue, &Colors::dodgerblue }, - { ColorNames::firebrick, &Colors::firebrick }, - { ColorNames::floralwhite, &Colors::floralwhite }, - { ColorNames::forestgreen, &Colors::forestgreen }, - { ColorNames::magenta, &Colors::magenta }, - { ColorNames::fuchsia, &Colors::fuchsia }, - { ColorNames::gainsboro, &Colors::gainsboro }, - { ColorNames::ghostwhite, &Colors::ghostwhite }, - { ColorNames::gold, &Colors::gold }, - { ColorNames::goldenrod, &Colors::goldenrod }, - { ColorNames::gray, &Colors::gray }, - { ColorNames::grey, &Colors::grey }, - { ColorNames::green, &Colors::green }, - { ColorNames::greenyellow, &Colors::greenyellow }, - { ColorNames::honeydew, &Colors::honeydew }, - { ColorNames::hotpink, &Colors::hotpink }, - { ColorNames::indianred, &Colors::indianred }, - { ColorNames::indigo, &Colors::indigo }, - { ColorNames::ivory, &Colors::ivory }, - { ColorNames::khaki, &Colors::khaki }, - { ColorNames::lavender, &Colors::lavender }, - { ColorNames::lavenderblush, &Colors::lavenderblush }, - { ColorNames::lawngreen, &Colors::lawngreen }, - { ColorNames::lemonchiffon, &Colors::lemonchiffon }, - { ColorNames::lightblue, &Colors::lightblue }, - { ColorNames::lightcoral, &Colors::lightcoral }, - { ColorNames::lightcyan, &Colors::lightcyan }, - { ColorNames::lightgoldenrodyellow, &Colors::lightgoldenrodyellow }, - { ColorNames::lightgray, &Colors::lightgray }, - { ColorNames::lightgrey, &Colors::lightgrey }, - { ColorNames::lightgreen, &Colors::lightgreen }, - { ColorNames::lightpink, &Colors::lightpink }, - { ColorNames::lightsalmon, &Colors::lightsalmon }, - { ColorNames::lightseagreen, &Colors::lightseagreen }, - { ColorNames::lightskyblue, &Colors::lightskyblue }, - { ColorNames::lightslategray, &Colors::lightslategray }, - { ColorNames::lightslategrey, &Colors::lightslategrey }, - { ColorNames::lightsteelblue, &Colors::lightsteelblue }, - { ColorNames::lightyellow, &Colors::lightyellow }, - { ColorNames::lime, &Colors::lime }, - { ColorNames::limegreen, &Colors::limegreen }, - { ColorNames::linen, &Colors::linen }, - { ColorNames::maroon, &Colors::maroon }, - { ColorNames::mediumaquamarine, &Colors::mediumaquamarine }, - { ColorNames::mediumblue, &Colors::mediumblue }, - { ColorNames::mediumorchid, &Colors::mediumorchid }, - { ColorNames::mediumpurple, &Colors::mediumpurple }, - { ColorNames::mediumseagreen, &Colors::mediumseagreen }, - { ColorNames::mediumslateblue, &Colors::mediumslateblue }, - { ColorNames::mediumspringgreen, &Colors::mediumspringgreen }, - { ColorNames::mediumturquoise, &Colors::mediumturquoise }, - { ColorNames::mediumvioletred, &Colors::mediumvioletred }, - { ColorNames::midnightblue, &Colors::midnightblue }, - { ColorNames::mintcream, &Colors::mintcream }, - { ColorNames::mistyrose, &Colors::mistyrose }, - { ColorNames::moccasin, &Colors::moccasin }, - { ColorNames::navajowhite, &Colors::navajowhite }, - { ColorNames::navy, &Colors::navy }, - { ColorNames::oldlace, &Colors::oldlace }, - { ColorNames::olive, &Colors::olive }, - { ColorNames::olivedrab, &Colors::olivedrab }, - { ColorNames::orange, &Colors::orange }, - { ColorNames::orangered, &Colors::orangered }, - { ColorNames::orchid, &Colors::orchid }, - { ColorNames::palegoldenrod, &Colors::palegoldenrod }, - { ColorNames::palegreen, &Colors::palegreen }, - { ColorNames::paleturquoise, &Colors::paleturquoise }, - { ColorNames::palevioletred, &Colors::palevioletred }, - { ColorNames::papayawhip, &Colors::papayawhip }, - { ColorNames::peachpuff, &Colors::peachpuff }, - { ColorNames::peru, &Colors::peru }, - { ColorNames::pink, &Colors::pink }, - { ColorNames::plum, &Colors::plum }, - { ColorNames::powderblue, &Colors::powderblue }, - { ColorNames::purple, &Colors::purple }, - { ColorNames::red, &Colors::red }, - { ColorNames::rosybrown, &Colors::rosybrown }, - { ColorNames::royalblue, &Colors::royalblue }, - { ColorNames::saddlebrown, &Colors::saddlebrown }, - { ColorNames::salmon, &Colors::salmon }, - { ColorNames::sandybrown, &Colors::sandybrown }, - { ColorNames::seagreen, &Colors::seagreen }, - { ColorNames::seashell, &Colors::seashell }, - { ColorNames::sienna, &Colors::sienna }, - { ColorNames::silver, &Colors::silver }, - { ColorNames::skyblue, &Colors::skyblue }, - { ColorNames::slateblue, &Colors::slateblue }, - { ColorNames::slategray, &Colors::slategray }, - { ColorNames::slategrey, &Colors::slategrey }, - { ColorNames::snow, &Colors::snow }, - { ColorNames::springgreen, &Colors::springgreen }, - { ColorNames::steelblue, &Colors::steelblue }, - { ColorNames::tan, &Colors::tan }, - { ColorNames::teal, &Colors::teal }, - { ColorNames::thistle, &Colors::thistle }, - { ColorNames::tomato, &Colors::tomato }, - { ColorNames::turquoise, &Colors::turquoise }, - { ColorNames::violet, &Colors::violet }, - { ColorNames::wheat, &Colors::wheat }, - { ColorNames::white, &Colors::white }, - { ColorNames::whitesmoke, &Colors::whitesmoke }, - { ColorNames::yellow, &Colors::yellow }, - { ColorNames::yellowgreen, &Colors::yellowgreen }, - { ColorNames::rebeccapurple, &Colors::rebeccapurple }, - { ColorNames::transparent, &Colors::transparent } - }; + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// - const Color_RGBA* name_to_color(const char* key) - { - return name_to_color(sass::string(key)); - } + const ColorRgba* name_to_color(const char* key) + { + return name_to_color(sass::string(key)); + } - const Color_RGBA* name_to_color(const sass::string& key) - { - // case insensitive lookup. See #2462 - sass::string lower = key; - Util::ascii_str_tolower(&lower); + const ColorRgba* name_to_color(const sass::string& key) + { + // case insensitive lookup. See #2462 + sass::string lcKey = key; + StringUtils::makeLowerCase(lcKey); - auto p = names_to_colors->find(lower); - if (p != names_to_colors->end()) { - return p->second; - } - return nullptr; - } + auto p = names_to_colors->find(lcKey); + if(p != names_to_colors->end()) + { + return p->second; + } - const char* color_to_name(const int key) - { - auto p = colors_to_names->find(key); - if (p != colors_to_names->end()) { - return p->second; - } - return nullptr; - } + return nullptr; + } - const char* color_to_name(const double key) - { - return color_to_name((int)key); - } + const char* color_to_name(const int key) + { + auto p = colors_to_names->find(key); + if(p != colors_to_names->end()) + { + sass::string rv = p->second; + // Match dart-sass output + if(rv == "magenta") + { + return "fuchsia"; + } + if(rv == "cyan") + { + return "aqua"; + } + return p->second; + } + return nullptr; + } - const char* color_to_name(const Color_RGBA& c) - { - double key = c.r() * 0x10000 - + c.g() * 0x100 - + c.b(); - return color_to_name(key); - } + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// -} +} // namespace Sass diff --git a/src/color_maps.hpp b/src/color_maps.hpp index c365f3ba2a..f5b5279ea9 100644 --- a/src/color_maps.hpp +++ b/src/color_maps.hpp @@ -1,9 +1,14 @@ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#ifndef SASS_COLOR_MAPS_HPP +#define SASS_COLOR_MAPS_HPP -#ifndef SASS_COLOR_MAPS_H -#define SASS_COLOR_MAPS_H +// sass.hpp must go before all system headers +// to get the __EXTENSIONS__ fix on Solaris. +#include "capi_sass.hpp" -#include -#include "ast.hpp" +#include "ast_values.hpp" namespace Sass { @@ -161,162 +166,160 @@ namespace Sass { } namespace Colors { - extern const Color_RGBA aliceblue; - extern const Color_RGBA antiquewhite; - extern const Color_RGBA cyan; - extern const Color_RGBA aqua; - extern const Color_RGBA aquamarine; - extern const Color_RGBA azure; - extern const Color_RGBA beige; - extern const Color_RGBA bisque; - extern const Color_RGBA black; - extern const Color_RGBA blanchedalmond; - extern const Color_RGBA blue; - extern const Color_RGBA blueviolet; - extern const Color_RGBA brown; - extern const Color_RGBA burlywood; - extern const Color_RGBA cadetblue; - extern const Color_RGBA chartreuse; - extern const Color_RGBA chocolate; - extern const Color_RGBA coral; - extern const Color_RGBA cornflowerblue; - extern const Color_RGBA cornsilk; - extern const Color_RGBA crimson; - extern const Color_RGBA darkblue; - extern const Color_RGBA darkcyan; - extern const Color_RGBA darkgoldenrod; - extern const Color_RGBA darkgray; - extern const Color_RGBA darkgrey; - extern const Color_RGBA darkgreen; - extern const Color_RGBA darkkhaki; - extern const Color_RGBA darkmagenta; - extern const Color_RGBA darkolivegreen; - extern const Color_RGBA darkorange; - extern const Color_RGBA darkorchid; - extern const Color_RGBA darkred; - extern const Color_RGBA darksalmon; - extern const Color_RGBA darkseagreen; - extern const Color_RGBA darkslateblue; - extern const Color_RGBA darkslategray; - extern const Color_RGBA darkslategrey; - extern const Color_RGBA darkturquoise; - extern const Color_RGBA darkviolet; - extern const Color_RGBA deeppink; - extern const Color_RGBA deepskyblue; - extern const Color_RGBA dimgray; - extern const Color_RGBA dimgrey; - extern const Color_RGBA dodgerblue; - extern const Color_RGBA firebrick; - extern const Color_RGBA floralwhite; - extern const Color_RGBA forestgreen; - extern const Color_RGBA magenta; - extern const Color_RGBA fuchsia; - extern const Color_RGBA gainsboro; - extern const Color_RGBA ghostwhite; - extern const Color_RGBA gold; - extern const Color_RGBA goldenrod; - extern const Color_RGBA gray; - extern const Color_RGBA grey; - extern const Color_RGBA green; - extern const Color_RGBA greenyellow; - extern const Color_RGBA honeydew; - extern const Color_RGBA hotpink; - extern const Color_RGBA indianred; - extern const Color_RGBA indigo; - extern const Color_RGBA ivory; - extern const Color_RGBA khaki; - extern const Color_RGBA lavender; - extern const Color_RGBA lavenderblush; - extern const Color_RGBA lawngreen; - extern const Color_RGBA lemonchiffon; - extern const Color_RGBA lightblue; - extern const Color_RGBA lightcoral; - extern const Color_RGBA lightcyan; - extern const Color_RGBA lightgoldenrodyellow; - extern const Color_RGBA lightgray; - extern const Color_RGBA lightgrey; - extern const Color_RGBA lightgreen; - extern const Color_RGBA lightpink; - extern const Color_RGBA lightsalmon; - extern const Color_RGBA lightseagreen; - extern const Color_RGBA lightskyblue; - extern const Color_RGBA lightslategray; - extern const Color_RGBA lightslategrey; - extern const Color_RGBA lightsteelblue; - extern const Color_RGBA lightyellow; - extern const Color_RGBA lime; - extern const Color_RGBA limegreen; - extern const Color_RGBA linen; - extern const Color_RGBA maroon; - extern const Color_RGBA mediumaquamarine; - extern const Color_RGBA mediumblue; - extern const Color_RGBA mediumorchid; - extern const Color_RGBA mediumpurple; - extern const Color_RGBA mediumseagreen; - extern const Color_RGBA mediumslateblue; - extern const Color_RGBA mediumspringgreen; - extern const Color_RGBA mediumturquoise; - extern const Color_RGBA mediumvioletred; - extern const Color_RGBA midnightblue; - extern const Color_RGBA mintcream; - extern const Color_RGBA mistyrose; - extern const Color_RGBA moccasin; - extern const Color_RGBA navajowhite; - extern const Color_RGBA navy; - extern const Color_RGBA oldlace; - extern const Color_RGBA olive; - extern const Color_RGBA olivedrab; - extern const Color_RGBA orange; - extern const Color_RGBA orangered; - extern const Color_RGBA orchid; - extern const Color_RGBA palegoldenrod; - extern const Color_RGBA palegreen; - extern const Color_RGBA paleturquoise; - extern const Color_RGBA palevioletred; - extern const Color_RGBA papayawhip; - extern const Color_RGBA peachpuff; - extern const Color_RGBA peru; - extern const Color_RGBA pink; - extern const Color_RGBA plum; - extern const Color_RGBA powderblue; - extern const Color_RGBA purple; - extern const Color_RGBA red; - extern const Color_RGBA rosybrown; - extern const Color_RGBA royalblue; - extern const Color_RGBA saddlebrown; - extern const Color_RGBA salmon; - extern const Color_RGBA sandybrown; - extern const Color_RGBA seagreen; - extern const Color_RGBA seashell; - extern const Color_RGBA sienna; - extern const Color_RGBA silver; - extern const Color_RGBA skyblue; - extern const Color_RGBA slateblue; - extern const Color_RGBA slategray; - extern const Color_RGBA slategrey; - extern const Color_RGBA snow; - extern const Color_RGBA springgreen; - extern const Color_RGBA steelblue; - extern const Color_RGBA tan; - extern const Color_RGBA teal; - extern const Color_RGBA thistle; - extern const Color_RGBA tomato; - extern const Color_RGBA turquoise; - extern const Color_RGBA violet; - extern const Color_RGBA wheat; - extern const Color_RGBA white; - extern const Color_RGBA whitesmoke; - extern const Color_RGBA yellow; - extern const Color_RGBA yellowgreen; - extern const Color_RGBA rebeccapurple; - extern const Color_RGBA transparent; + extern const ColorRgba aliceblue; + extern const ColorRgba antiquewhite; + extern const ColorRgba cyan; + extern const ColorRgba aqua; + extern const ColorRgba aquamarine; + extern const ColorRgba azure; + extern const ColorRgba beige; + extern const ColorRgba bisque; + extern const ColorRgba black; + extern const ColorRgba blanchedalmond; + extern const ColorRgba blue; + extern const ColorRgba blueviolet; + extern const ColorRgba brown; + extern const ColorRgba burlywood; + extern const ColorRgba cadetblue; + extern const ColorRgba chartreuse; + extern const ColorRgba chocolate; + extern const ColorRgba coral; + extern const ColorRgba cornflowerblue; + extern const ColorRgba cornsilk; + extern const ColorRgba crimson; + extern const ColorRgba darkblue; + extern const ColorRgba darkcyan; + extern const ColorRgba darkgoldenrod; + extern const ColorRgba darkgray; + extern const ColorRgba darkgrey; + extern const ColorRgba darkgreen; + extern const ColorRgba darkkhaki; + extern const ColorRgba darkmagenta; + extern const ColorRgba darkolivegreen; + extern const ColorRgba darkorange; + extern const ColorRgba darkorchid; + extern const ColorRgba darkred; + extern const ColorRgba darksalmon; + extern const ColorRgba darkseagreen; + extern const ColorRgba darkslateblue; + extern const ColorRgba darkslategray; + extern const ColorRgba darkslategrey; + extern const ColorRgba darkturquoise; + extern const ColorRgba darkviolet; + extern const ColorRgba deeppink; + extern const ColorRgba deepskyblue; + extern const ColorRgba dimgray; + extern const ColorRgba dimgrey; + extern const ColorRgba dodgerblue; + extern const ColorRgba firebrick; + extern const ColorRgba floralwhite; + extern const ColorRgba forestgreen; + extern const ColorRgba magenta; + extern const ColorRgba fuchsia; + extern const ColorRgba gainsboro; + extern const ColorRgba ghostwhite; + extern const ColorRgba gold; + extern const ColorRgba goldenrod; + extern const ColorRgba gray; + extern const ColorRgba grey; + extern const ColorRgba green; + extern const ColorRgba greenyellow; + extern const ColorRgba honeydew; + extern const ColorRgba hotpink; + extern const ColorRgba indianred; + extern const ColorRgba indigo; + extern const ColorRgba ivory; + extern const ColorRgba khaki; + extern const ColorRgba lavender; + extern const ColorRgba lavenderblush; + extern const ColorRgba lawngreen; + extern const ColorRgba lemonchiffon; + extern const ColorRgba lightblue; + extern const ColorRgba lightcoral; + extern const ColorRgba lightcyan; + extern const ColorRgba lightgoldenrodyellow; + extern const ColorRgba lightgray; + extern const ColorRgba lightgrey; + extern const ColorRgba lightgreen; + extern const ColorRgba lightpink; + extern const ColorRgba lightsalmon; + extern const ColorRgba lightseagreen; + extern const ColorRgba lightskyblue; + extern const ColorRgba lightslategray; + extern const ColorRgba lightslategrey; + extern const ColorRgba lightsteelblue; + extern const ColorRgba lightyellow; + extern const ColorRgba lime; + extern const ColorRgba limegreen; + extern const ColorRgba linen; + extern const ColorRgba maroon; + extern const ColorRgba mediumaquamarine; + extern const ColorRgba mediumblue; + extern const ColorRgba mediumorchid; + extern const ColorRgba mediumpurple; + extern const ColorRgba mediumseagreen; + extern const ColorRgba mediumslateblue; + extern const ColorRgba mediumspringgreen; + extern const ColorRgba mediumturquoise; + extern const ColorRgba mediumvioletred; + extern const ColorRgba midnightblue; + extern const ColorRgba mintcream; + extern const ColorRgba mistyrose; + extern const ColorRgba moccasin; + extern const ColorRgba navajowhite; + extern const ColorRgba navy; + extern const ColorRgba oldlace; + extern const ColorRgba olive; + extern const ColorRgba olivedrab; + extern const ColorRgba orange; + extern const ColorRgba orangered; + extern const ColorRgba orchid; + extern const ColorRgba palegoldenrod; + extern const ColorRgba palegreen; + extern const ColorRgba paleturquoise; + extern const ColorRgba palevioletred; + extern const ColorRgba papayawhip; + extern const ColorRgba peachpuff; + extern const ColorRgba peru; + extern const ColorRgba pink; + extern const ColorRgba plum; + extern const ColorRgba powderblue; + extern const ColorRgba purple; + extern const ColorRgba red; + extern const ColorRgba rosybrown; + extern const ColorRgba royalblue; + extern const ColorRgba saddlebrown; + extern const ColorRgba salmon; + extern const ColorRgba sandybrown; + extern const ColorRgba seagreen; + extern const ColorRgba seashell; + extern const ColorRgba sienna; + extern const ColorRgba silver; + extern const ColorRgba skyblue; + extern const ColorRgba slateblue; + extern const ColorRgba slategray; + extern const ColorRgba slategrey; + extern const ColorRgba snow; + extern const ColorRgba springgreen; + extern const ColorRgba steelblue; + extern const ColorRgba tan; + extern const ColorRgba teal; + extern const ColorRgba thistle; + extern const ColorRgba tomato; + extern const ColorRgba turquoise; + extern const ColorRgba violet; + extern const ColorRgba wheat; + extern const ColorRgba white; + extern const ColorRgba whitesmoke; + extern const ColorRgba yellow; + extern const ColorRgba yellowgreen; + extern const ColorRgba rebeccapurple; + extern const ColorRgba transparent; } - const Color_RGBA* name_to_color(const char*); - const Color_RGBA* name_to_color(const sass::string&); + const ColorRgba* name_to_color(const char*); + const ColorRgba* name_to_color(const sass::string&); const char* color_to_name(const int); - const char* color_to_name(const Color_RGBA&); - const char* color_to_name(const double); } diff --git a/src/compiler.cpp b/src/compiler.cpp new file mode 100644 index 0000000000..03988e38c9 --- /dev/null +++ b/src/compiler.cpp @@ -0,0 +1,1003 @@ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#include "compiler.hpp" + +#include "json.hpp" +#include "eval.hpp" +#include "output.hpp" +#include "sources.hpp" +#include "stylesheet.hpp" +#include "capi_import.hpp" +#include "capi_importer.hpp" +#include "ast_imports.hpp" +#include "parser_scss.hpp" +#include "parser_sass.hpp" +#include "parser_css.hpp" +#include "exceptions.hpp" +#include "remove_placeholders.hpp" + +// Functions to register +#include "fn_maps.hpp" +#include "fn_meta.hpp" +#include "fn_math.hpp" +#include "fn_lists.hpp" +#include "fn_texts.hpp" +#include "fn_colors.hpp" +#include "fn_selectors.hpp" + +#include "b64/encode.hpp" +#include "preloader.hpp" +#include "plugins.hpp" +#include "file.hpp" + +#include "debugger.hpp" + +#include +#include +#ifdef _MSC_VER +#include +#endif + +namespace Sass { + + Compiler::~Compiler() + { + for (auto& mod : modules) { + delete mod.second; + } + #ifdef DEBUG_MSVC_CRT_MEM + _CrtMemState state; + _CrtMemState delta; + _CrtMemCheckpoint(&state); + if (_CrtMemDifference(&delta, &memState, &state)) + _CrtMemDumpStatistics(&delta); + #endif + } + + Compiler::Compiler() : + varRoot(*this), + state(SASS_COMPILER_CREATED), + output_path("stream://stdout"), + entry_point(), + footer(nullptr), + srcmap(nullptr), + error() + { + #ifdef DEBUG_MSVC_CRT_MEM + _CrtMemCheckpoint(&memState); + #endif + } + + // Get path of compilation entry point + // Returns the resolved/absolute path + sass::string Compiler::getInputPath() const + { + // Simply return absolute path of entry point + if (entry_point && entry_point->getAbsPath()) { + return entry_point->getAbsPath(); + } + // Fall back to stdin + return "stream://stdin"; + } + // EO getInputPath + + // Get the output path for this compilation + // Can be explicit or deducted from input path + sass::string Compiler::getOutputPath() const + { + // Specific output path was provided + if (!output_path.empty()) { + return output_path; + } + // Otherwise deduct it from input path + sass::string path(getInputPath()); + // Check if input is coming from stdin + if (path == "stream://stdin") { + return "stream://stdout"; + } + // Otherwise remove the file extension + size_t dotpos = path.find_last_of('.'); + if (dotpos != sass::string::npos) { + path.erase(dotpos); + } + // Use css extension + path += ".css"; + return path; + } + // EO getOutputPath + + // Check if we should write output file + bool Compiler::hasOutputFile() const + { + return !output_path.empty() && + output_path != "stream://stdout"; + } + // EO hasOutputFile + + // Check if we should write srcmap file + bool Compiler::hasSrcMapFile() const + { + return mapopt.mode == SASS_SRCMAP_CREATE || + mapopt.mode == SASS_SRCMAP_EMBED_LINK; + } + // EO hasSrcMapFile + + void Compiler::parse() + { + // Only allow phase right after parsing + if (state == SASS_COMPILER_CREATED) { + // Check for valid entry point + if (entry_point == nullptr) { + throw Exception::ParserException(*this, + "No entry-point to compile given"); + } + // Do initial loading + entry_point->loadIfNeeded(*this); + // Now parse the entry point stylesheet + sheet = parseRoot(entry_point); + // Update the compiler state + state = SASS_COMPILER_PARSED; + } + } + + // parse root block from includes (Move to compiler) + CssRootObj Compiler::compileRoot(bool plainCss) + { + RootObj root = sheet; + if (root == nullptr) return {}; + + #ifdef DEBUG_SHARED_PTR + // Enable reference tracking + RefCounted::taint = true; + #endif + + // abort if there is no data + if (included_sources.size() == 0) return {}; + // abort on invalid root + if (root.isNull()) return {}; + + Eval eval(*this, *this, plainCss); + + // root->extender = SASS_MEMORY_NEW(ExtensionStore, ExtensionStore::NORMAL, eval.logger); + + //debug_ast(root); + + CssRootObj compiled = eval.acceptRoot2(root); + + // debug_ast(compiled, "== "); + + // Extension unsatisfied; + // // check that all extends were used + // if (root->checkForUnsatisfiedExtends3(unsatisfied)) { + // // throw Exception::UnsatisfiedExtend(*this, unsatisfied); + // } + + // clean up by removing empty placeholders + // ToDo: maybe we can do this somewhere else? + RemovePlaceholders remove_placeholders; + remove_placeholders.visitCssRoot(compiled); // 3% + + #ifdef DEBUG_SHARED_PTR + // Enable reference tracking + RefCounted::taint = false; + #endif + + // return processed tree + return compiled; + } + // EO compileRoot + + void Compiler::compile() + { + // Only act right after parsing + if (state == SASS_COMPILER_PARSED) { + // Compile the parsed ast-tree + compiled = compileRoot(false); + // Update the compiler state + state = SASS_COMPILER_COMPILED; + } + } + + OutputBuffer Compiler::renderCss() + { + // Create the emitter object + Output emitter(*this); + emitter.reserve(1024 * 1024); // 1MB + emitter.in_declaration = false; + // Start the render process + if (compiled != nullptr) { + emitter.visitCssRoot(compiled); + } + // Finish emitter stream + emitter.finalize(); + // Update the compiler state + state = SASS_COMPILER_RENDERED; + // Move buffer from stream + return emitter.getBuffer(); + } + // EO renderCss + + // Case 1) output to stdout, source map must be fully inline + // Case 2) output to path, source map output is deducted from it + char* Compiler::renderSrcMapLink(const SourceMap& source_map) + { + // Source map json must already be there + if (srcmap == nullptr) return nullptr; + // Check if we output to stdout (any link would be useless) + if (output_path.empty() || output_path == "stream://stdout") { + if (mapopt.path.empty() || mapopt.path == "stream://stdout") { + // Instead always embed the source-map on stdout + return renderEmbeddedSrcMap(source_map); + } + } + // Create resulting footer and return a copy + return sass_copy_string("\n/*# sourceMappingURL=" + + File::abs2rel(mapopt.path, mapopt.origin) + " */"); + + } + // EO renderSrcMapLink + + // Memory returned by this function must be freed by caller via `sass_free_c_string` + char* Compiler::renderEmbeddedSrcMap(const SourceMap& source_map) + { + // Source map json must already be there + if (srcmap == nullptr) return nullptr; + // Encode json to base64 + sass::istream is(srcmap); + sass::ostream buffer; + buffer << "\n/*# sourceMappingURL="; + buffer << "data:application/json;base64,"; + base64::encoder E; + E.encode(is, buffer); + buffer << " */"; + return sass_copy_string(buffer.str()); + } + // EO renderEmbeddedSrcMap + + // Render an additional message if warnings were suppressed + void Compiler::reportSuppressedWarnings() + { + if (suppressed > 0) { + sass::sstream message; + message << getTerm(Terminal::yellow); + if (suppressed == 1) { + message << "Additionally, one similar warning was suppressed!\n"; + } + else { + message << "Additionally, " << suppressed + << " similar warnings were suppressed!\n"; + } + message << getTerm(Terminal::reset); + warnings += message.str(); + reported.reset(); + suppressed = 0; + } + } + // EO reportSuppressedWarnings + + // Memory returned by this function must be freed by caller via `sass_free_c_string` + char* Compiler::renderSrcMapJson(const SourceMap& source_map) + { + // Create the emitter object + // Sass::OutputBuffer buffer; + + /**********************************************/ + // Create main object to render json + /**********************************************/ + JsonNode* json_srcmap = json_mkobject(); + + /**********************************************/ + // Create the source-map version information + /**********************************************/ + json_append_member(json_srcmap, "version", json_mknumber(3)); + + /**********************************************/ + // Create file reference to whom our mappings apply + /**********************************************/ + sass::string origin(mapopt.origin); + origin = File::abs2rel(origin, CWD()); + JsonNode* json_file_name = json_mkstring(origin.c_str()); + json_append_member(json_srcmap, "file", json_file_name); + + /**********************************************/ + // pass-through source_map_root option + /**********************************************/ + if (!mapopt.root.empty()) { + json_append_member(json_srcmap, "sourceRoot", + json_mkstring(mapopt.root.c_str())); + } + + /**********************************************/ + // Create the included sources array + /**********************************************/ + JsonNode* json_sources = json_mkarray(); + for (size_t i = 0; i < included_sources.size(); ++i) { + const SourceData* source(included_sources[i]); + sass::string path(source->getAbsPath()); + path = File::rel2abs(path, ".", CWD()); + // Optionally convert to file urls + if (mapopt.file_urls) { + if (path[0] == '/') { + // ends up with three slashes + path = "file://" + path; + } + else { + // needs additional slash + path = "file:///" + path; + } + // Append item to json array + json_append_element(json_sources, + json_mkstring(path.c_str())); + } + else { + path = File::abs2rel(path, ".", CWD()); + // Append item to json array + json_append_element(json_sources, + json_mkstring(path.c_str())); + } + } + json_append_member(json_srcmap, "sources", json_sources); + + // add a relative link to the source map output file + // srcmap_links88.emplace_back(abs2rel(abs_path, file88, CWD)); + + /**********************************************/ + // Check if we have any includes to render + /**********************************************/ + if (mapopt.embed_contents) { + JsonNode* json_contents = json_mkarray(); + for (size_t i = 0; i < included_sources.size(); ++i) { + const SourceData* source = included_sources[i]; + JsonNode* json_content = json_mkstring(source->content()); + json_append_element(json_contents, json_content); + } + json_append_member(json_srcmap, "sourcesContent", json_contents); + } + + /**********************************************/ + // So far we have no implementation for names + /**********************************************/ + json_append_member(json_srcmap, "names", json_mkarray()); + + /**********************************************/ + // Create source remapping lookup table + // For source-maps we need to output sources in + // consecutive manner, but we might have used various + // different stylesheets from the prolonged context + /**********************************************/ + std::unordered_map idxremap; + for (auto& source : included_sources) { + idxremap.insert(std::make_pair( + source->getSrcIdx(), + idxremap.size())); + } + + /**********************************************/ + // Finally render the actual source mappings + // Remap context srcidx to consecutive ordering + /**********************************************/ + sass::string mappings(source_map.render(idxremap)); + JsonNode* json_mappings = json_mkstring(mappings.c_str()); + json_append_member(json_srcmap, "mappings", json_mappings); + + /**********************************************/ + // Render the json and return result + // Memory must be freed by consumer! + /**********************************************/ + char* data = json_stringify(json_srcmap, "\t"); + json_delete(json_srcmap); + return data; + + } + // EO renderSrcMapJson + + ///////////////////////////////////////////////////////////////////////// + // @param imp_path The relative or custom path for be imported + // @param pstate SourceSpan where import occurred (parent context) + // @param rule The backing ImportRule that is added to the document + // @param importers Array of custom importers/headers to go through + // @param singleton Whether to use all importers or only first successful + ///////////////////////////////////////////////////////////////////////// + bool Compiler::callCustomLoader(const sass::string& imp_path, SourceSpan& pstate, + ImportRule* rule, const sass::vector& importers, bool singleton) + { + // unique counter + size_t count = 0; + // need one correct import + bool has_import = false; + + const char* ctx_path = pstate.getAbsPath(); + + // Process custom importers and headers. + // They must be presorted by priorities. + for (struct SassImporter* importer : importers) { + + // Get the external importer function + SassImporterLambda fn = importer->lambda; + + // Call the external function, then check what it returned + struct SassImportList* imports = fn(imp_path.c_str(), importer, this->wrap()); + // External provider want to handle this + if (imports != nullptr) { + + // Get the list of possible imports + // A list with zero items does nothing + while (sass_import_list_size(imports) > 0) { + // Increment counter + ++count; + // Consume the first item from the list + struct SassImport* entry = sass_import_list_shift(imports); + Import& import = Import::unwrap(entry); + // Create a unique path to use as key + sass::string uniq_path = imp_path; + // Append counter to the path + // Note: only for headers! + if (!singleton && count) { + sass::sstream path_strm; + path_strm << uniq_path << ":" << count; + uniq_path = path_strm.str(); + } + // create the importer struct + ImportRequest importer(uniq_path, ctx_path, false); + // Check if importer returned and error state. + // Should only happen if importer knows that + // this import must only be handled by itself. + if (sass_import_get_error_message(entry)) { + BackTraces& traces = *this; + traces.push_back(BackTrace(pstate)); + Exception::CustomImportError err(traces, + sass_import_get_error_message(entry)); + sass_delete_import_list(imports); + sass_delete_import(entry); + throw err; + } + // Source must be set, even if the actual data is not loaded yet + else if (SourceDataObj source = import.source) { + const char* rel_path = import.getImpPath(); + const char* abs_path = import.getAbsPath(); + if (import.isLoaded()) { + // Resolved abs_path should be set by custom importer + // Use the created uniq_path as fall-back (enforce?) + sass::string path_key(abs_path ? abs_path : uniq_path); + // Import is ready to be served + if (import.syntax == SASS_IMPORT_AUTO) + import.syntax = SASS_IMPORT_SCSS; + ImportStackFrame iframe(*this, &import); + Root* sheet = registerImport(&import); + // Add a dynamic import to the import rule + auto inc = SASS_MEMORY_NEW(IncludeImport, + pstate, ctx_path, path_key, &import); + inc->root47(sheet); + rule->append(inc); + } + // Only a path was returned + // Try to load it like normal + else if (abs_path || rel_path) { + // Create a copy for possible error reporting + sass::string path(abs_path ? abs_path : rel_path); + // Pass it on as if it was a regular import + ImportRequest request(path, ctx_path, false); + // Search for valid imports (e.g. partials) on the file-system + // Returns multiple valid results for ambiguous import path + const sass::vector& resolved(findIncludes(request, true)); + // Error if no file to import was found + if (resolved.empty()) { + BackTraces& traces = *this; + traces.push_back(BackTrace(pstate)); + Exception::CustomImportNotFound err(traces, path); + sass_delete_import_list(imports); + sass_delete_import(entry); + throw err; + } + // Error if multiple files to import were found + else if (resolved.size() > 1) { + BackTraces& traces = *this; + traces.push_back(BackTrace(pstate)); + Exception::CustomImportAmbigous err(traces, path); + sass_delete_import_list(imports); + sass_delete_import(entry); + throw err; + } + // We made sure exactly one entry was found, load its content + if (ImportObj loaded = loadImport(resolved[0])) { + ImportStackFrame iframe(*this, loaded); + Root* sheet = registerImport(loaded); + const sass::string& url(resolved[0].abs_path); + auto inc = SASS_MEMORY_NEW(IncludeImport, + pstate, ctx_path, url, &import); + inc->root47(sheet); + rule->append(inc); + } + // Import failed to load + else { + BackTraces& traces = *this; + traces.push_back(BackTrace(pstate)); + Exception::CustomImportLoadError err(traces, path); + sass_delete_import_list(imports); + sass_delete_import(entry); + throw err; + } + + } + } + sass_delete_import(entry); + } + + // Deallocate the returned memory + sass_delete_import_list(imports); + // Set success flag + has_import = true; + // Break out of loop? + if (singleton) break; + } + } + // Return result + return has_import; + } + // EO callCustomLoader + + void Compiler::applyCustomHeaders(StatementVector& statements, SourceSpan pstate) + { + // create a custom import to resolve headers + ImportRuleObj rule = SASS_MEMORY_NEW(ImportRule, pstate); + // dispatch headers which will add custom functions + // custom headers are added to the import instance + if (callCustomHeaders("sass://header", pstate, rule)) { + statements.push_back(rule.ptr()); + } + } + + + ///////////////////////////////////////////////////////////////////////// + // Interface for built in functions + ///////////////////////////////////////////////////////////////////////// + + // Register built-in mixin and associate mixin callback + uint32_t Compiler::createBuiltInMixin(const EnvKey& name, + const sass::string& signature, SassFnSig cb, bool acceptsContent) + { + EnvRoot root(*this); + SourceDataObj source = SASS_MEMORY_NEW(SourceString, + "sass://signature", "(" + signature + ")"); + CallableSignature* args = CallableSignature::parse(*this, source); + auto callable = SASS_MEMORY_NEW(BuiltInCallable, name, args, cb); + callable->acceptsContent(acceptsContent); + auto& mixins(varRoot.intMixin); + uint32_t offset((uint32_t)mixins.size()); + varRoot.intMixin.push_back(callable); + varRoot.privateMixOffset = offset + 1; + return offset; + } + + // Register built-in variable with the associated value + uint32_t Compiler::createBuiltInVariable(const EnvKey& name, Value* value) + { + EnvRoot root(*this); + auto& variables(varRoot.intVariables); + uint32_t offset((uint32_t)variables.size()); + varRoot.intVariables.push_back(value); + varRoot.privateVarOffset = offset + 1; + return offset; + } + // EO createBuiltInVariable + + // Register built-in function with only one parameter list. + uint32_t Compiler::createBuiltInFunction(const EnvKey& name, + const sass::string& signature, SassFnSig cb) + { + EnvRoot root(*this); + SourceDataObj source = SASS_MEMORY_NEW(SourceString, + "sass://signature", "(" + signature + ")"); + auto args = CallableSignature::parse(*this, source); + auto callable = SASS_MEMORY_NEW(BuiltInCallable, name, args, cb); + auto& functions(varRoot.intFunction); + uint32_t offset((uint32_t)functions.size()); + varRoot.intFunction.push_back(callable); + varRoot.privateFnOffset = offset + 1; + return offset; + } + // EO createBuiltInFunction + + // Register internal function with only one parameter list. + // Same as `createBuiltInFunction`, but marks it internal + uint32_t Compiler::createInternalFunction(const EnvKey& name, + const sass::string& signature, SassFnSig cb) + { + EnvRoot root(*this); + SourceDataObj source = SASS_MEMORY_NEW(SourceString, + "sass://signature", "(" + signature + ")"); + auto args = CallableSignature::parse(*this, source); + auto callable = SASS_MEMORY_NEW(BuiltInCallable, name, args, cb, true); + auto& functions(varRoot.intFunction); + uint32_t offset((uint32_t)functions.size()); + varRoot.intFunction.push_back(callable); + varRoot.privateFnOffset = offset + 1; + return offset; + } + // EO createInternalFunction + + + // Register built-in functions that can take different + // functional arguments (best suited will be chosen). + uint32_t Compiler::createBuiltInOverloadFns(const EnvKey& name, + const sass::vector>& overloads) + { + SassFnPairs pairs; + for (auto overload : overloads) { + EnvRoot root(*this); + SourceDataObj source = SASS_MEMORY_NEW(SourceString, + "sass://signature", "(" + overload.first + ")"); + auto args = CallableSignature::parse(*this, source); + pairs.emplace_back(std::make_pair(args, overload.second)); + } + auto callable = SASS_MEMORY_NEW(BuiltInCallables, name, pairs); + auto& functions(varRoot.intFunction); + uint32_t offset((uint32_t)functions.size()); + varRoot.intFunction.push_back(callable); + varRoot.privateFnOffset = offset + 1; + return offset; + } + // EO registerBuiltInOverloadFns + + // Register built-in function with only one parameter list. + uint32_t Compiler::registerBuiltInFunction(const EnvKey& name, + const sass::string& signature, SassFnSig cb) + { + auto fn = createBuiltInFunction(name, signature, cb); + return varRoot.idxs->fnIdxs[name] = fn; // Expose + } + // EO registerBuiltInFunction + + // Register built-in function with only one parameter list. + uint32_t Compiler::registerInternalFunction(const EnvKey& name, + const sass::string& signature, SassFnSig cb) + { + auto fn = createInternalFunction(name, signature, cb); + return varRoot.idxs->fnIdxs[name] = fn; // Expose + } + // EO registerBuiltInFunction + + void Compiler::exposeFunction(const EnvKey& name, uint32_t idx) + { + varRoot.idxs->fnIdxs[name] = idx; + } + + // Register built-in functions that can take different + // functional arguments (best suited will be chosen). + uint32_t Compiler::registerBuiltInOverloadFns(const EnvKey& name, + const sass::vector>& overloads) + { + auto fn = createBuiltInOverloadFns(name, overloads); + return varRoot.idxs->fnIdxs[name] = fn; // Expose + } + // EO registerBuiltInOverloadFns + + ///////////////////////////////////////////////////////////////////////// + // Interface for external custom functions + ///////////////////////////////////////////////////////////////////////// + + // Register an external custom sass function on the global scope. + // Main entry point for custom functions passed through the C-API. + void Compiler::registerCustomFunction(struct SassFunction* function) + { + EnvRoot root(*this); + SourceDataObj source = SASS_MEMORY_NEW(SourceString, + "sass://signature", sass::string(function->signature)); + ScssParser parser(*this, source.ptr()); + ExternalCallable* callable = + parser.parseExternalCallable(); + callable->lambda(function->lambda); + callable->cookie(function->cookie); + auto& functions(varRoot.intFunction); + uint32_t offset((uint32_t)functions.size()); + varRoot.intFunction.push_back(callable); + varRoot.idxs->fnIdxs[callable->name()] = offset; + varRoot.privateFnOffset = offset + 1; + } + // EO registerCustomFunction + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // Invoke parser according to import format + RootObj Compiler::parseSource(ImportObj import) + { + Root* root = nullptr; + if (import->syntax == SASS_IMPORT_CSS) + { + CssParser parser(*this, import->source); + root = parser.parseRoot(); + } + else if (import->syntax == SASS_IMPORT_SASS) + { + SassParser parser(*this, import->source); + root = parser.parseRoot(); + } + else { + ScssParser parser(*this, import->source); + root = parser.parseRoot(); + } + if (root) root->import = import; + return root; + } + // EO parseSource + + // Parse the import (updates syntax flag if AUTO was set) + // Results will be stored at `sheets[source->getAbsPath()]` + Root* Compiler::registerImport(ImportObj import) + { + + SassImportSyntax& format(import->syntax); + const SourceDataObj& source(import->source); + const sass::string& abs_path(source->getAbsPath()); + + // ToDo: We don't take format into account + auto cached = sheets.find(abs_path); + if (cached != sheets.end()) { + return cached->second; + } + + // Assign unique index to the source + source->setSrcIdx(included_sources.size()); + // Add source to global include array + included_sources.emplace_back(source); + + // Auto detect input file format + if (format == SASS_IMPORT_AUTO) { + using namespace StringUtils; + if (endsWithIgnoreCase(abs_path, ".css", 4)) { + format = SASS_IMPORT_CSS; + } + else if (endsWithIgnoreCase(abs_path, ".sass", 5)) { + format = SASS_IMPORT_SASS; + } + else if (endsWithIgnoreCase(abs_path, ".scss", 5)) { + format = SASS_IMPORT_SCSS; + } + else if (abs_path != "stream://stdin") { + throw Exception::UnknownImport(callStack); + } + } + + // Invoke correct parser according to format + RootObj stylesheet = parseSource(import); + + // Put the parsed stylesheet into the map + sheets.insert({ abs_path, stylesheet }); + + if (stylesheet.ptr() != nullptr) { + stylesheet->import = import; + } + + stylesheet->extender = SASS_MEMORY_NEW(ExtensionStore, ExtensionStore::NORMAL, *this); + // std::cerr << "!! Create import store " << abs_path << " => " << stylesheet->extender.ptr() << "\n"; + + // Return pointer, it is already managed + // Don't call detach, as it could leak then + return stylesheet.ptr(); + + } + // EO registerImport + + // Called once to register all built-in functions. + // This will invoke parsing for parameter lists. + void Compiler::loadBuiltInFunctions() + { + Functions::Meta::registerFunctions(*this); + Functions::Math::registerFunctions(*this); + Functions::Maps::registerFunctions(*this); + Functions::Lists::registerFunctions(*this); + Functions::Colors::registerFunctions(*this); + Functions::Texts::registerFunctions(*this); + Functions::Selectors::registerFunctions(*this); + } + // EO loadBuiltInFunctions + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + Root* Compiler::parseRoot(ImportObj import) + { + + // Insert ourself onto the sources cache + sources.insert({ import->getAbsPath(), import }); + + // Register all built-in functions + loadBuiltInFunctions(); + + #ifdef DEBUG_SHARED_PTR + // Enable reference tracking + RefCounted::taint = true; + #endif + + // load and register import + ImportStackFrame iframe(*this, import); + Root* sheet = registerImport(import); + + #ifdef DEBUG_SHARED_PTR + // Disable reference tracking + RefCounted::taint = false; + #endif + + // Return root node + return sheet; + + } + // EO parseRoot + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + ImportStackFrame::ImportStackFrame(Compiler& compiler, Import* import) : + compiler(compiler) + { + + auto& source(import->source); + auto& stack(compiler.import_stack); + + // std::cerr << "IMPORT " << sass::string(stack.size(), ' ') << source->getAbsPath() << "\n"; + + stack.push_back(import); + + if (stack.size() < 2) return; + + + // check existing import stack for possible recursion + for (size_t i = stack.size() - 2;; --i) { + const SourceDataObj parent = stack[i]->source; + if (std::strcmp(parent->getAbsPath(), source->getAbsPath()) == 0) { + // make path relative to the current directory + sass::string msg("An @import loop has been found:"); + // callStackFrame frame(compiler, import->pstate()); + for (size_t n = i; n < stack.size() - 1; ++n) { + msg += "\n " + sass::string(File::abs2rel(stack[n]->source->getAbsPath(), CWD(), CWD())) + + " imports " + sass::string(File::abs2rel(stack[n + 1]->source->getAbsPath(), CWD(), CWD())); + } + // implement error throw directly until we + // decided how to handle full stack traces + throw Exception::RuntimeException(compiler, msg); + } + // Exit condition + if (i == 0) break; + } + + } + + ImportStackFrame::~ImportStackFrame() + { + compiler.import_stack.pop_back(); + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // Add additional include paths, which can be path separated + void Compiler::addIncludePaths(const sass::string& paths) + { + // Check if we have anything to do + if (paths.empty()) return; + // Load plugins from all paths + sass::vector split = + StringUtils::split(paths, PATH_SEP, true); + for (sass::string& path : split) { + if (*path.rbegin() != '/') path += '/'; + includePaths.emplace_back(path); + } + } + // EO addIncludePaths + + // Load plugins from paths, which can be path separated + void Compiler::loadPlugins(const sass::string& paths) + { + Plugins plugins(*this); + // Check if we have anything to do + if (paths.empty()) return; + // Load plugins from all paths + sass::vector split = + StringUtils::split(paths, PATH_SEP, true); + for (sass::string& path : split) { + if (*path.rbegin() != '/') path += '/'; + plugins.load_plugins(path); + } + // Sort the merged arrays by callback priorities + sort(cHeaders.begin(), cHeaders.end(), cmpImporterPrio); + sort(cImporters.begin(), cImporters.end(), cmpImporterPrio); + } + // EO loadPlugins + + ///////////////////////////////////////////////////////////////////////// + // Helpers for `sass_prepare_context` + // Obsolete when c_ctx and cpp_ctx are merged. + ///////////////////////////////////////////////////////////////////////// + + void Compiler::addCustomHeader(struct SassImporter* header) + { + if (header == nullptr) return; + cHeaders.emplace_back(header); + // need to sort the array afterwards (no big deal) + sort(cHeaders.begin(), cHeaders.end(), cmpImporterPrio); + } + + void Compiler::addCustomImporter(struct SassImporter* importer) + { + if (importer == nullptr) return; + cImporters.emplace_back(importer); + // need to sort the array afterwards (no big deal) + sort(cImporters.begin(), cImporters.end(), cmpImporterPrio); + } + + void Compiler::addCustomFunction(struct SassFunction* function) + { + if (function == nullptr) return; + cFunctions.emplace_back(function); + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // Implementation for `sass_compiler_find_file` + sass::string Compiler::findFile(const sass::string& path) + { + // Get last import entry for current base + Import& import = import_stack.back(); + // create the vector with paths to lookup + sass::vector incpaths(1 + includePaths.size()); + incpaths.emplace_back(File::dir_name(import.source->getAbsPath())); + incpaths.insert(incpaths.end(), includePaths.begin(), includePaths.end()); + return File::find_file(path, CWD(), incpaths, fileExistsCache); + } + + // Look for all possible filename variants (e.g. partials) + // Returns all results (e.g. for ambiguous valid imports) + const sass::vector& Compiler::findIncludes(const ImportRequest& import, bool forImport) + { + // Try to find cached result first + auto it = resolveCache.find(import); + if (it != resolveCache.end()) return it->second; + + // make sure we resolve against an absolute path + sass::string base_path(File::rel2abs(import.base_path, ".", CWD())); + + // first try to resolve the load path relative to the base path + sass::vector& vec(resolveCache[import]); + + vec = File::resolve_includes(base_path, import.imp_path, CWD(), forImport, fileExistsCache); + + // then search in every include path (but only if nothing found yet) + for (size_t i = 0, S = includePaths.size(); vec.size() == 0 && i < S; ++i) + { + sass::vector resolved(File::resolve_includes( + includePaths[i], import.imp_path, CWD(), forImport, fileExistsCache)); + vec.insert(vec.end(), resolved.begin(), resolved.end()); + } + // return vector + return vec; + } + + // Load import from the file-system and create source object + Import* Compiler::loadImport(const ResolvedImport& import) + { + // Try to find the item in the cache first + auto cached = sources.find(import.abs_path); + if (cached != sources.end()) return cached->second; + // Try to read source and (ToDo) optional mappings + if (ImportObj loaded = File::read_import(import)) { + sources.insert({ import.abs_path, loaded }); + return loaded.ptr(); + } + // Throw error if read has failed + throw Exception::IoError(*this, + "File not found or unreadable", + File::abs2rel(import.abs_path)); + } + // EO loadImport + + // Update precision and epsilon etc. + void Compiler::setPrecision(int precision) + { + Logger::setPrecision(precision); + OutputOptions::setPrecision(precision); + } + +} diff --git a/src/compiler.hpp b/src/compiler.hpp new file mode 100644 index 0000000000..b7e20e0608 --- /dev/null +++ b/src/compiler.hpp @@ -0,0 +1,373 @@ +#ifndef SASS_COMPILER_H +#define SASS_COMPILER_H + +// sass.hpp must go before all system headers +// to get the __EXTENSIONS__ fix on Solaris. +#include "capi_sass.hpp" + +#include +#include "logger.hpp" +#include "ast_css.hpp" +#include "stylesheet.hpp" +#include "capi_error.hpp" +#include "capi_import.hpp" +#include "capi_importer.hpp" +#include "capi_compiler.hpp" +#include "capi_traces.hpp" +#include "source_map.hpp" + +namespace Sass { + + // Helper function to sort header and importer arrays by priorities + inline bool cmpImporterPrio(struct SassImporter* i, struct SassImporter* j) + { + return sass_importer_get_priority(i) > sass_importer_get_priority(j); + } + + // The main compiler context object holding config and results + class Compiler final : public OutputOptions, public Logger { + + private: + + #ifdef DEBUG_MSVC_CRT_MEM + _CrtMemState memState; + #endif + + public: + + // Checking if a file exists can be quite extensive + // Keep an internal map to avoid repeated system calls + std::unordered_map fileExistsCache; + + // Keep cache of resolved import filenames + // Key is a pair of previous + import path + std::unordered_map> resolveCache; + + // Include paths are local to context since we need to know + // it for lookups during parsing. You may reset this for + // another compilation when reusing the context. + sass::vector includePaths; + + public: + + Root* modctx3 = nullptr; + + WithConfig* wconfig = nullptr; + + EnvKeyMap modules; + + protected: + + // Functions in order of appearance + // Same order needed for function stack + sass::vector fnList; + + public: + + // sass::vector* withConfig = nullptr; + virtual ~Compiler(); + + // EnvKeyMap withConfig; + + // Sheets are filled after resources are parsed + // This could be shared, should go to engine!? + // ToDo: should be case insensitive on windows? + std::map sheets; + + // Only used to cache `loadImport` calls + std::map sources; + + // Additional C-API stuff for interaction + sass::vector cHeaders; + sass::vector cImporters; + sass::vector cFunctions; + + public: + + // The import stack during evaluation phase + sass::vector import_stack; + + // List of all sources that have been included + sass::vector included_sources; + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + ///////////////////////////////////////////////////////////////////////// + // Helpers for `sass_prepare_context` + ///////////////////////////////////////////////////////////////////////// + + void addCustomHeader(struct SassImporter* header); + void addCustomImporter(struct SassImporter* importer); + void addCustomFunction(struct SassFunction* function); + + ///////////////////////////////////////////////////////////////////////// + // Some simple delegations to variable root for runtime queries + ///////////////////////////////////////////////////////////////////////// + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // Load plugins from path, which can be path separated + void loadPlugins(const sass::string& paths); + + // Add additional include paths, which can be path separated + void addIncludePaths(const sass::string& paths); + + // Load import from the file-system and create source object + // Results will be stored at `sources[source->getAbsPath()]` + Import* loadImport(const ResolvedImport& import); + + // Implementation for `sass_compiler_find_file` + // Looks for the file in regard to the current + // import and then looks in all include folders. + sass::string findFile(const sass::string& path); + + // Look for all possible filename variants (e.g. partials) + // Returns all results (e.g. for ambiguous but valid imports) + const sass::vector& findIncludes(const ImportRequest& import, bool forImport); + + + public: + + EnvRefs* getCurrentScope() const { + if (varRoot.stack.empty()) return nullptr; + return varRoot.stack.back(); + } + + EnvRefs* getCurrentModule() const { + if (varRoot.stack.empty()) return nullptr; + auto current = varRoot.stack.back(); + while (current->pscope) { + if (current->module) break; + current = current->pscope; + } + return current; + } + + BuiltInMod& createModule(const sass::string& name) { + auto it = modules.find(name); + if (it != modules.end()) { + return *it->second; + } + BuiltInMod* module = new BuiltInMod(varRoot); + modules.insert({ name, module }); + return *module; + } + + BuiltInMod* getModule(const sass::string& name) { + auto it = modules.find(name); + if (it == modules.end()) return nullptr; + return it->second; + } + + + // Flag if we currently have a with-config + bool hasWithConfig = false; + + // Stack of environment frames. New frames are appended + // when parser encounters a new environment scoping. + sass::vector varStack3312; + + // The root environment where parsed root variables + // and (custom) functions plus mixins are registered. + EnvRoot varRoot; // Must be after varStack! + + // Functions only for evaluation phase (C-API functions and eval itself) + // CallableObj* findFunction(const EnvKey& name) { return varRoot.findFunction(name); } + + // The current state the compiler is in. + enum SassCompilerState state; + + // Where we want to store the output. + // Source-map path is deducted from it. + // Defaults to `stream://stdout`. + sass::string output_path; + + // main entry point for compilation + ImportObj entry_point; + + // Parsed ast-tree + RootObj sheet; + + // Evaluated ast-tree + CssRootObj compiled; + + // The rendered css content. + sass::string content; + + // Rendered warnings and debugs. They can be emitted at any stage. + // Therefore we make a copy into our string after each stage from + // the actual logger instance (created by context). This is needed + // in order to return a `c_str` on the API from the output-stream. + sass::string warnings; + + // The rendered output footer. This includes the + // rendered css comment footer for the source-map. + // Append after output for the full output document. + char* footer; + + // The rendered source map. This is what an implementor + // would normally write out to the `output.css.map` file + char* srcmap; + + // Runtime error + SassError error; + + // Constructor + Compiler(); + + // Parse ast tree + void parse(); + + // Compile parsed tree + void compile(); + + // Render compiled tree + OutputBuffer renderCss(); + + // Get path of compilation entry point + // Returns the resolved/absolute path + sass::string getInputPath() const; + + // Get the output path for this compilation + // Can be explicit or deducted from input path + sass::string getOutputPath() const; + + // ToDO, maybe belongs to compiler? + CssRootObj compileRoot(bool plainCss); + + char* renderSrcMapJson(const SourceMap& source_map); + + char* renderSrcMapLink(const SourceMap& source_map); + + char* renderEmbeddedSrcMap(const SourceMap& source_map); + + void reportSuppressedWarnings(); + + // Check if we should write output file + bool hasOutputFile() const; + + // Check if we should write srcmap file + bool hasSrcMapFile() const; + + ///////////////////////////////////////////////////////////////////////// + // Register built-in function with only one parameter list. + ///////////////////////////////////////////////////////////////////////// + uint32_t createBuiltInFunction(const EnvKey& name, + const sass::string& signature, SassFnSig cb); + uint32_t registerBuiltInFunction(const EnvKey& name, + const sass::string& signature, SassFnSig cb); + uint32_t createInternalFunction(const EnvKey& name, + const sass::string& signature, SassFnSig cb); + + uint32_t registerInternalFunction(const EnvKey& name, + const sass::string& signature, SassFnSig cb); + + void exposeFunction(const EnvKey& name, uint32_t idx); + + uint32_t createBuiltInVariable(const EnvKey& name, Value* value); + + // Only a few mixins will accept a content block + uint32_t createBuiltInMixin(const EnvKey& name, + const sass::string& signature, SassFnSig cb, + bool acceptsContent = false); + + ///////////////////////////////////////////////////////////////////////// + // Register built-in functions that can take different + // functional arguments (best suited will be chosen). + ///////////////////////////////////////////////////////////////////////// + uint32_t createBuiltInOverloadFns(const EnvKey& name, + const sass::vector>& overloads); + uint32_t registerBuiltInOverloadFns(const EnvKey& name, + const sass::vector>& overloads); + + ///////////////////////////////////////////////////////////////////////// + // @param imp_path The relative or custom path for be imported + // @param pstate SourceSpan where import occurred (parent context) + // @param rule The backing ImportRule that is added to the document + ///////////////////////////////////////////////////////////////////////// + bool callCustomHeaders(const sass::string& imp_path, SourceSpan& pstate, ImportRule* rule) + { + return callCustomLoader(imp_path, pstate, rule, cHeaders, false); + }; + + ///////////////////////////////////////////////////////////////////////// + // @param imp_path The relative or custom path for be imported + // @param pstate SourceSpan where import occurred (parent context) + // @param rule The backing ImportRule that is added to the document + ///////////////////////////////////////////////////////////////////////// + bool callCustomImporters(const sass::string& imp_path, SourceSpan& pstate, ImportRule* rule) + { + return callCustomLoader(imp_path, pstate, rule, cImporters, true); + }; + + // Parse the import (updates syntax flag if AUTO was set) + // Results will be stored at `sheets[source->getAbsPath()]` + Root* registerImport(ImportObj import); + + // Called by parserStylesheet on the very first parse call + void applyCustomHeaders(StatementVector& root, SourceSpan pstate); + + protected: + + ///////////////////////////////////////////////////////////////////////// + // Called once to register all built-in functions. + // This will invoke parsing for parameter lists. + ///////////////////////////////////////////////////////////////////////// + void loadBuiltInFunctions(); + + Root* parseRoot(ImportObj import); + + private: + + ///////////////////////////////////////////////////////////////////////// + // @param imp_path The relative or custom path for be imported + // @param pstate SourceSpan where import occurred (parent context) + // @param rule The backing ImportRule that is added to the document + // @param importers Array of custom importers/headers to go through + // @param singleton Whether to use all importers or only first successful + ///////////////////////////////////////////////////////////////////////// + bool callCustomLoader(const sass::string& imp_path, SourceSpan& pstate, ImportRule* rule, + const sass::vector& importers, bool singletone = true); + + public: + ///////////////////////////////////////////////////////////////////////// + // Register an external custom sass function on the global scope. + // Main entry point for custom functions passed through the C-API. + // The function you pass in will be taken over and freed by us! + ///////////////////////////////////////////////////////////////////////// + void registerCustomFunction(struct SassFunction* function); + + // Invoke parser according to import format + RootObj parseSource(ImportObj source); + + public: + + void setPrecision(int precision); + + CAPI_WRAPPER(Compiler, SassCompiler); + + }; + + class ImportStackFrame + { + private: + + Compiler& compiler; + + public: + + ImportStackFrame( + Compiler& compiler, + Import* import); + + ~ImportStackFrame(); + + }; + + +} + + +#endif diff --git a/src/constants.cpp b/src/constants.cpp index 8d9b64a4a8..09bac2c54e 100644 --- a/src/constants.cpp +++ b/src/constants.cpp @@ -1,199 +1,110 @@ -// sass.hpp must go before all system headers to get the -// __EXTENSIONS__ fix on Solaris. -#include "sass.hpp" - +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ #include "constants.hpp" +#include "terminal.hpp" + namespace Sass { - namespace Constants { - extern const unsigned long MaxCallStack = 1024; + namespace Constants { // https://github.com/sass/libsass/issues/592 // https://developer.mozilla.org/en-US/docs/Web/CSS/Specificity // https://github.com/sass/sass/issues/1495#issuecomment-61189114 - extern const unsigned long Specificity_Star = 0; - extern const unsigned long Specificity_Universal = 0; - extern const unsigned long Specificity_Element = 1; - extern const unsigned long Specificity_Base = 1000; - extern const unsigned long Specificity_Class = 1000; - extern const unsigned long Specificity_Attr = 1000; - extern const unsigned long Specificity_Pseudo = 1000; - extern const unsigned long Specificity_ID = 1000000; - - extern const int UnificationOrder_Element = 1; - extern const int UnificationOrder_Id = 2; - extern const int UnificationOrder_Class = 2; - extern const int UnificationOrder_Attribute = 3; - extern const int UnificationOrder_PseudoClass = 4; - extern const int UnificationOrder_Wrapped = 5; - extern const int UnificationOrder_PseudoElement = 6; - extern const int UnificationOrder_Placeholder = 7; - - // sass keywords - extern const char at_root_kwd[] = "@at-root"; - extern const char import_kwd[] = "@import"; - extern const char mixin_kwd[] = "@mixin"; - extern const char function_kwd[] = "@function"; - extern const char return_kwd[] = "@return"; - extern const char include_kwd[] = "@include"; - extern const char content_kwd[] = "@content"; - extern const char extend_kwd[] = "@extend"; - extern const char if_kwd[] = "@if"; - extern const char else_kwd[] = "@else"; - extern const char if_after_else_kwd[] = "if"; - extern const char for_kwd[] = "@for"; - extern const char from_kwd[] = "from"; - extern const char to_kwd[] = "to"; - extern const char of_kwd[] = "of"; - extern const char through_kwd[] = "through"; - extern const char each_kwd[] = "@each"; - extern const char in_kwd[] = "in"; - extern const char while_kwd[] = "@while"; - extern const char warn_kwd[] = "@warn"; - extern const char error_kwd[] = "@error"; - extern const char debug_kwd[] = "@debug"; - extern const char default_kwd[] = "default"; - extern const char global_kwd[] = "global"; - extern const char null_kwd[] = "null"; - extern const char optional_kwd[] = "optional"; - extern const char with_kwd[] = "with"; - extern const char without_kwd[] = "without"; - extern const char all_kwd[] = "all"; - extern const char rule_kwd[] = "rule"; - - // css standard units - extern const char em_kwd[] = "em"; - extern const char ex_kwd[] = "ex"; - extern const char px_kwd[] = "px"; - extern const char cm_kwd[] = "cm"; - extern const char mm_kwd[] = "mm"; - extern const char pt_kwd[] = "pt"; - extern const char pc_kwd[] = "pc"; - extern const char deg_kwd[] = "deg"; - extern const char rad_kwd[] = "rad"; - extern const char grad_kwd[] = "grad"; - extern const char turn_kwd[] = "turn"; - extern const char ms_kwd[] = "ms"; - extern const char s_kwd[] = "s"; - extern const char Hz_kwd[] = "Hz"; - extern const char kHz_kwd[] = "kHz"; - - // vendor prefixes - extern const char vendor_opera_kwd[] = "-o-"; - extern const char vendor_webkit_kwd[] = "-webkit-"; - extern const char vendor_mozilla_kwd[] = "-moz-"; - extern const char vendor_ms_kwd[] = "-ms-"; - extern const char vendor_khtml_kwd[] = "-khtml-"; - - // css functions and keywords - extern const char charset_kwd[] = "@charset"; - extern const char media_kwd[] = "@media"; - extern const char supports_kwd[] = "@supports"; - extern const char keyframes_kwd[] = "keyframes"; - extern const char only_kwd[] = "only"; - extern const char rgb_fn_kwd[] = "rgb("; - extern const char url_fn_kwd[] = "url("; - extern const char url_kwd[] = "url"; - // extern const char url_prefix_fn_kwd[] = "url-prefix("; - extern const char important_kwd[] = "important"; - extern const char pseudo_not_fn_kwd[] = ":not("; - extern const char even_kwd[] = "even"; - extern const char odd_kwd[] = "odd"; - extern const char progid_kwd[] = "progid"; - extern const char expression_kwd[] = "expression"; - extern const char calc_fn_kwd[] = "calc"; - - extern const char almost_any_value_class[] = "\"'#!;{}"; - - // css selector keywords - extern const char sel_deep_kwd[] = "/deep/"; - - // css attribute-matching operators - extern const char tilde_equal[] = "~="; - extern const char pipe_equal[] = "|="; - extern const char caret_equal[] = "^="; - extern const char dollar_equal[] = "$="; - extern const char star_equal[] = "*="; - - // relational & logical operators and constants - extern const char and_kwd[] = "and"; - extern const char or_kwd[] = "or"; - extern const char not_kwd[] = "not"; - extern const char gt[] = ">"; - extern const char gte[] = ">="; - extern const char lt[] = "<"; - extern const char lte[] = "<="; - extern const char eq[] = "=="; - extern const char neq[] = "!="; - extern const char true_kwd[] = "true"; - extern const char false_kwd[] = "false"; - - // definition keywords - extern const char using_kwd[] = "using"; - - // miscellaneous punctuation and delimiters - extern const char percent_str[] = "%"; - extern const char empty_str[] = ""; - extern const char slash_slash[] = "//"; - extern const char slash_star[] = "/*"; - extern const char star_slash[] = "*/"; - extern const char hash_lbrace[] = "#{"; - extern const char rbrace[] = "}"; - extern const char rparen[] = ")"; - extern const char sign_chars[] = "-+"; - extern const char op_chars[] = "-+"; - extern const char hyphen[] = "-"; - extern const char ellipsis[] = "..."; - // extern const char url_space_chars[] = " \t\r\n\f"; - // type names - extern const char numeric_name[] = "numeric value"; - extern const char number_name[] = "number"; - extern const char percentage_name[] = "percentage"; - extern const char dimension_name[] = "numeric dimension"; - extern const char string_name[] = "string"; - extern const char bool_name[] = "bool"; - extern const char color_name[] = "color"; - extern const char list_name[] = "list"; - extern const char map_name[] = "map"; - extern const char arglist_name[] = "arglist"; - - // constants for uri parsing (RFC 3986 Appendix A.) - extern const char uri_chars[] = ":;/?!%&#@|[]{}'`^\"*+-.,_=~"; - extern const char real_uri_chars[] = "#%&"; - - extern const char selector_combinator_child[] = ">"; - extern const char selector_combinator_general[] = "~"; - extern const char selector_combinator_adjacent[] = "+"; - - // some specific constant character classes - // they must be static to be useable by lexer - extern const char static_ops[] = "*/%"; - // some character classes for the parser - extern const char selector_list_delims[] = "){};!"; - extern const char complex_selector_delims[] = ",){};!"; - extern const char selector_combinator_ops[] = "+~>"; - // optional modifiers for alternative compare context - extern const char attribute_compare_modifiers[] = "~|^$*"; - extern const char selector_lookahead_ops[] = "*&%,()[]"; - - // byte order marks - // (taken from http://en.wikipedia.org/wiki/Byte_order_mark) - extern const unsigned char utf_8_bom[] = { 0xEF, 0xBB, 0xBF }; - extern const unsigned char utf_16_bom_be[] = { 0xFE, 0xFF }; - extern const unsigned char utf_16_bom_le[] = { 0xFF, 0xFE }; - extern const unsigned char utf_32_bom_be[] = { 0x00, 0x00, 0xFE, 0xFF }; - extern const unsigned char utf_32_bom_le[] = { 0xFF, 0xFE, 0x00, 0x00 }; - extern const unsigned char utf_7_bom_1[] = { 0x2B, 0x2F, 0x76, 0x38 }; - extern const unsigned char utf_7_bom_2[] = { 0x2B, 0x2F, 0x76, 0x39 }; - extern const unsigned char utf_7_bom_3[] = { 0x2B, 0x2F, 0x76, 0x2B }; - extern const unsigned char utf_7_bom_4[] = { 0x2B, 0x2F, 0x76, 0x2F }; - extern const unsigned char utf_7_bom_5[] = { 0x2B, 0x2F, 0x76, 0x38, 0x2D }; - extern const unsigned char utf_1_bom[] = { 0xF7, 0x64, 0x4C }; - extern const unsigned char utf_ebcdic_bom[] = { 0xDD, 0x73, 0x66, 0x73 }; - extern const unsigned char scsu_bom[] = { 0x0E, 0xFE, 0xFF }; - extern const unsigned char bocu_1_bom[] = { 0xFB, 0xEE, 0x28 }; - extern const unsigned char gb_18030_bom[] = { 0x84, 0x31, 0x95, 0x33 }; + namespace Specificity + { + extern const unsigned long Star = 0; + extern const unsigned long Universal = 0; + extern const unsigned long Element = 1; + extern const unsigned long Base = 1000; + extern const unsigned long Class = 1000; + extern const unsigned long Attr = 1000; + extern const unsigned long Pseudo = 1000; + extern const unsigned long ID = 1000000; + } + + + // http://en.wikipedia.org/wiki/Byte_order_mark + namespace BOM { + extern const unsigned char utf_8[] = { 0xEF, 0xBB, 0xBF }; + extern const unsigned char utf_16_be[] = { 0xFE, 0xFF }; + extern const unsigned char utf_16_le[] = { 0xFF, 0xFE }; + extern const unsigned char utf_32_be[] = { 0x00, 0x00, 0xFE, 0xFF }; + extern const unsigned char utf_32_le[] = { 0xFF, 0xFE, 0x00, 0x00 }; + extern const unsigned char utf_7_1[] = { 0x2B, 0x2F, 0x76, 0x38 }; + extern const unsigned char utf_7_2[] = { 0x2B, 0x2F, 0x76, 0x39 }; + extern const unsigned char utf_7_3[] = { 0x2B, 0x2F, 0x76, 0x2B }; + extern const unsigned char utf_7_4[] = { 0x2B, 0x2F, 0x76, 0x2F }; + extern const unsigned char utf_7_5[] = { 0x2B, 0x2F, 0x76, 0x38, 0x2D }; + extern const unsigned char utf_1[] = { 0xF7, 0x64, 0x4C }; + extern const unsigned char utf_ebcdic[] = { 0xDD, 0x73, 0x66, 0x73 }; + extern const unsigned char scsu[] = { 0x0E, 0xFE, 0xFF }; + extern const unsigned char bocu_1[] = { 0xFB, 0xEE, 0x28 }; + extern const unsigned char gb_18030[] = { 0x84, 0x31, 0x95, 0x33 }; + } + + + namespace Terminal { + const char reset[] = "\033[m"; + const char bold[] = "\033[1m"; + const char red[] = "\033[31m"; + const char green[] = "\033[32m"; + const char yellow[] = "\033[33m"; + const char blue[] = "\033[34m"; + const char magenta[] = "\033[35m"; + const char cyan[] = "\033[36m"; + const char white[] = "\033[37m"; + // Intensified on windows + const char bold_red[] = "\033[1;31m"; + const char bold_green[] = "\033[1;32m"; + const char bold_yellow[] = "\033[1;33m"; + const char bold_blue[] = "\033[1;34m"; + const char bold_magenta[] = "\033[1;35m"; + const char bold_cyan[] = "\033[1;36m"; + const char bold_white[] = "\033[1;37m"; + const char bg_red[] = "\033[41m"; + const char bg_green[] = "\033[42m"; + const char bg_yellow[] = "\033[43m"; + const char bg_blue[] = "\033[44m"; + const char bg_magenta[] = "\033[45m"; + const char bg_cyan[] = "\033[46m"; + const char bg_white[] = "\033[47m"; + // Intensified on windows + const char bg_bold_red[] = "\033[1;41m"; + const char bg_bold_green[] = "\033[1;42m"; + const char bg_bold_yellow[] = "\033[1;43m"; + const char bg_bold_blue[] = "\033[1;44m"; + const char bg_bold_magenta[] = "\033[1;45m"; + const char bg_bold_cyan[] = "\033[1;46m"; + const char bg_bold_white[] = "\033[1;47m"; + + } + + namespace String { + + const char empty[] = ""; + + } + + namespace Math { + + const double M_E = 2.71828182845904523536; // e + const double M_LOG2E = 1.44269504088896340736; // log2(e) + const double M_LOG10E = 0.434294481903251827651; // log10(e) + const double M_LN2 = 0.693147180559945309417; // ln(2) + const double M_LN10 = 2.30258509299404568402; // ln(10) + const double M_PI = 3.14159265358979323846; // pi + const double M_PI_2 = 1.57079632679489661923; // pi/2 + const double M_PI_4 = 0.785398163397448309616; // pi/4 + const double M_1_PI = 0.318309886183790671538; // 1/pi + const double M_2_PI = 0.636619772367581343076; // 2/pi + const double M_2_SQRTPI = 1.12837916709551257390; // 2/sqrt(pi) + const double M_SQRT2 = 1.41421356237309504880; // sqrt(2) + const double M_SQRT1_2 = 0.707106781186547524401; // 1/sqrt(2) + const double RAD_TO_DEG = 57.295779513082320876798154814105; + + } } } diff --git a/src/constants.hpp b/src/constants.hpp index 81ada27494..8c7056e480 100644 --- a/src/constants.hpp +++ b/src/constants.hpp @@ -1,200 +1,121 @@ -#ifndef SASS_CONSTANTS_H -#define SASS_CONSTANTS_H +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#ifndef SASS_CONSTANTS_HPP +#define SASS_CONSTANTS_HPP -namespace Sass { - namespace Constants { +// sass.hpp must go before all system headers +// to get the __EXTENSIONS__ fix on Solaris. +#include "capi_sass.hpp" - // The maximum call stack that can be created - extern const unsigned long MaxCallStack; +namespace Sass +{ + namespace Constants + { + + // https://github.com/sass/libsass/issues/592 // https://developer.mozilla.org/en-US/docs/Web/CSS/Specificity - // The following list of selectors is by increasing specificity: - extern const unsigned long Specificity_Star; - extern const unsigned long Specificity_Universal; - extern const unsigned long Specificity_Element; - extern const unsigned long Specificity_Base; - extern const unsigned long Specificity_Class; - extern const unsigned long Specificity_Attr; - extern const unsigned long Specificity_Pseudo; - extern const unsigned long Specificity_ID; - - // Selector unification order; - extern const int UnificationOrder_Element; - extern const int UnificationOrder_Id; - extern const int UnificationOrder_Class; - extern const int UnificationOrder_Attribute; - extern const int UnificationOrder_PseudoClass; - extern const int UnificationOrder_Wrapped; - extern const int UnificationOrder_PseudoElement; - extern const int UnificationOrder_Placeholder; - - // sass keywords - extern const char at_root_kwd[]; - extern const char import_kwd[]; - extern const char mixin_kwd[]; - extern const char function_kwd[]; - extern const char return_kwd[]; - extern const char include_kwd[]; - extern const char content_kwd[]; - extern const char extend_kwd[]; - extern const char if_kwd[]; - extern const char else_kwd[]; - extern const char if_after_else_kwd[]; - extern const char for_kwd[]; - extern const char from_kwd[]; - extern const char to_kwd[]; - extern const char of_kwd[]; - extern const char through_kwd[]; - extern const char each_kwd[]; - extern const char in_kwd[]; - extern const char while_kwd[]; - extern const char warn_kwd[]; - extern const char error_kwd[]; - extern const char debug_kwd[]; - extern const char default_kwd[]; - extern const char global_kwd[]; - extern const char null_kwd[]; - extern const char optional_kwd[]; - extern const char with_kwd[]; - extern const char without_kwd[]; - extern const char all_kwd[]; - extern const char rule_kwd[]; - - // css standard units - extern const char em_kwd[]; - extern const char ex_kwd[]; - extern const char px_kwd[]; - extern const char cm_kwd[]; - extern const char mm_kwd[]; - extern const char pt_kwd[]; - extern const char pc_kwd[]; - extern const char deg_kwd[]; - extern const char rad_kwd[]; - extern const char grad_kwd[]; - extern const char turn_kwd[]; - extern const char ms_kwd[]; - extern const char s_kwd[]; - extern const char Hz_kwd[]; - extern const char kHz_kwd[]; - - // vendor prefixes - extern const char vendor_opera_kwd[]; - extern const char vendor_webkit_kwd[]; - extern const char vendor_mozilla_kwd[]; - extern const char vendor_ms_kwd[]; - extern const char vendor_khtml_kwd[]; - - // css functions and keywords - extern const char charset_kwd[]; - extern const char media_kwd[]; - extern const char supports_kwd[]; - extern const char keyframes_kwd[]; - extern const char only_kwd[]; - extern const char rgb_fn_kwd[]; - extern const char url_fn_kwd[]; - extern const char url_kwd[]; - // extern const char url_prefix_fn_kwd[]; - extern const char important_kwd[]; - extern const char pseudo_not_fn_kwd[]; - extern const char even_kwd[]; - extern const char odd_kwd[]; - extern const char progid_kwd[]; - extern const char expression_kwd[]; - extern const char calc_fn_kwd[]; - - // char classes for "regular expressions" - extern const char almost_any_value_class[]; - - // css selector keywords - extern const char sel_deep_kwd[]; - - // css attribute-matching operators - extern const char tilde_equal[]; - extern const char pipe_equal[]; - extern const char caret_equal[]; - extern const char dollar_equal[]; - extern const char star_equal[]; - - // relational & logical operators and constants - extern const char and_kwd[]; - extern const char or_kwd[]; - extern const char not_kwd[]; - extern const char gt[]; - extern const char gte[]; - extern const char lt[]; - extern const char lte[]; - extern const char eq[]; - extern const char neq[]; - extern const char true_kwd[]; - extern const char false_kwd[]; - - // definition keywords - extern const char using_kwd[]; - - // miscellaneous punctuation and delimiters - extern const char percent_str[]; - extern const char empty_str[]; - extern const char slash_slash[]; - extern const char slash_star[]; - extern const char star_slash[]; - extern const char hash_lbrace[]; - extern const char rbrace[]; - extern const char rparen[]; - extern const char sign_chars[]; - extern const char op_chars[]; - extern const char hyphen[]; - extern const char ellipsis[]; - // extern const char url_space_chars[]; - - // type names - extern const char numeric_name[]; - extern const char number_name[]; - extern const char percentage_name[]; - extern const char dimension_name[]; - extern const char string_name[]; - extern const char bool_name[]; - extern const char color_name[]; - extern const char list_name[]; - extern const char map_name[]; - extern const char arglist_name[]; - - // constants for uri parsing (RFC 3986 Appendix A.) - extern const char uri_chars[]; - extern const char real_uri_chars[]; - - // constants for selector combinators - extern const char selector_combinator_child[]; - extern const char selector_combinator_general[]; - extern const char selector_combinator_adjacent[]; - - // some specific constant character classes - // they must be static to be useable by lexer - extern const char static_ops[]; - extern const char selector_list_delims[]; - extern const char complex_selector_delims[]; - extern const char selector_combinator_ops[]; - extern const char attribute_compare_modifiers[]; - extern const char selector_lookahead_ops[]; - - // byte order marks - // (taken from http://en.wikipedia.org/wiki/Byte_order_mark) - extern const unsigned char utf_8_bom[]; - extern const unsigned char utf_16_bom_be[]; - extern const unsigned char utf_16_bom_le[]; - extern const unsigned char utf_32_bom_be[]; - extern const unsigned char utf_32_bom_le[]; - extern const unsigned char utf_7_bom_1[]; - extern const unsigned char utf_7_bom_2[]; - extern const unsigned char utf_7_bom_3[]; - extern const unsigned char utf_7_bom_4[]; - extern const unsigned char utf_7_bom_5[]; - extern const unsigned char utf_1_bom[]; - extern const unsigned char utf_ebcdic_bom[]; - extern const unsigned char scsu_bom[]; - extern const unsigned char bocu_1_bom[]; - extern const unsigned char gb_18030_bom[]; - - } -} + // https://github.com/sass/sass/issues/1495#issuecomment-61189114 + namespace Specificity + { + + extern const unsigned long Star; + extern const unsigned long Universal; + extern const unsigned long Element; + extern const unsigned long Base; + extern const unsigned long Class; + extern const unsigned long Attr; + extern const unsigned long Pseudo; + extern const unsigned long ID; + + } // namespace Specificity + + // http://en.wikipedia.org/wiki/Byte_order_mark + namespace BOM + { + + extern const unsigned char utf_8[]; + extern const unsigned char utf_16_be[]; + extern const unsigned char utf_16_le[]; + extern const unsigned char utf_32_be[]; + extern const unsigned char utf_32_le[]; + extern const unsigned char utf_7_1[]; + extern const unsigned char utf_7_2[]; + extern const unsigned char utf_7_3[]; + extern const unsigned char utf_7_4[]; + extern const unsigned char utf_7_5[]; + extern const unsigned char utf_1[]; + extern const unsigned char utf_ebcdic[]; + extern const unsigned char scsu[]; + extern const unsigned char bocu_1[]; + extern const unsigned char gb_18030[]; + + } // namespace BOM + + namespace Terminal + { + + extern const char reset[]; + extern const char bold[]; + extern const char red[]; + extern const char green[]; + extern const char yellow[]; + extern const char blue[]; + extern const char magenta[]; + extern const char cyan[]; + extern const char white[]; + extern const char bold_red[]; + extern const char bold_green[]; + extern const char bold_yellow[]; + extern const char bold_blue[]; + extern const char bold_magenta[]; + extern const char bold_cyan[]; + extern const char bold_white[]; + extern const char bg_red[]; + extern const char bg_green[]; + extern const char bg_yellow[]; + extern const char bg_blue[]; + extern const char bg_magenta[]; + extern const char bg_cyan[]; + extern const char bg_white[]; + extern const char bg_bold_red[]; + extern const char bg_bold_green[]; + extern const char bg_bold_yellow[]; + extern const char bg_bold_blue[]; + extern const char bg_bold_magenta[]; + extern const char bg_bold_cyan[]; + extern const char bg_bold_white[]; + + } // namespace Terminal + + namespace String + { + + extern const char empty[]; + + } // namespace String + + namespace Math + { + extern const double M_E; + extern const double M_LOG2E; + extern const double M_LOG10E; + extern const double M_LN2; + extern const double M_LN10; + extern const double M_PI; + extern const double M_PI_2; + extern const double M_PI_4; + extern const double M_1_PI; + extern const double M_2_PI; + extern const double M_2_SQRTPI; + extern const double M_SQRT2; + extern const double M_SQRT1_2; + extern const double RAD_TO_DEG; + } + + } // namespace Constants + +} // namespace Sass #endif diff --git a/src/context.cpp b/src/context.cpp deleted file mode 100644 index 7fa7d78183..0000000000 --- a/src/context.cpp +++ /dev/null @@ -1,863 +0,0 @@ -// sass.hpp must go before all system headers to get the -// __EXTENSIONS__ fix on Solaris. -#include "sass.hpp" -#include "ast.hpp" - -#include "remove_placeholders.hpp" -#include "sass_functions.hpp" -#include "check_nesting.hpp" -#include "fn_selectors.hpp" -#include "fn_strings.hpp" -#include "fn_numbers.hpp" -#include "fn_colors.hpp" -#include "fn_miscs.hpp" -#include "fn_lists.hpp" -#include "fn_maps.hpp" -#include "context.hpp" -#include "expand.hpp" -#include "parser.hpp" -#include "cssize.hpp" -#include "source.hpp" - -namespace Sass { - using namespace Constants; - using namespace File; - using namespace Sass; - - inline bool sort_importers (const Sass_Importer_Entry& i, const Sass_Importer_Entry& j) - { return sass_importer_get_priority(i) > sass_importer_get_priority(j); } - - static sass::string safe_input(const char* in_path) - { - if (in_path == nullptr || in_path[0] == '\0') return "stdin"; - return in_path; - } - - static sass::string safe_output(const char* out_path, sass::string input_path) - { - if (out_path == nullptr || out_path[0] == '\0') { - if (input_path.empty()) return "stdout"; - return input_path.substr(0, input_path.find_last_of(".")) + ".css"; - } - return out_path; - } - - Context::Context(struct Sass_Context& c_ctx) - : CWD(File::get_cwd()), - c_options(c_ctx), - entry_path(""), - head_imports(0), - plugins(), - emitter(c_options), - - ast_gc(), - strings(), - resources(), - sheets(), - import_stack(), - callee_stack(), - traces(), - extender(Extender::NORMAL, traces), - c_compiler(NULL), - - c_headers (sass::vector()), - c_importers (sass::vector()), - c_functions (sass::vector()), - - indent (safe_str(c_options.indent, " ")), - linefeed (safe_str(c_options.linefeed, "\n")), - - input_path (make_canonical_path(safe_input(c_options.input_path))), - output_path (make_canonical_path(safe_output(c_options.output_path, input_path))), - source_map_file (make_canonical_path(safe_str(c_options.source_map_file, ""))), - source_map_root (make_canonical_path(safe_str(c_options.source_map_root, ""))) - - { - - // Sass 3.4: The current working directory will no longer be placed onto the Sass load path by default. - // If you need the current working directory to be available, set SASS_PATH=. in your shell's environment. - // include_paths.push_back(CWD); - - // collect more paths from different options - collect_include_paths(c_options.include_path); - collect_include_paths(c_options.include_paths); - collect_plugin_paths(c_options.plugin_path); - collect_plugin_paths(c_options.plugin_paths); - - // load plugins and register custom behaviors - for(auto plug : plugin_paths) plugins.load_plugins(plug); - for(auto fn : plugins.get_headers()) c_headers.push_back(fn); - for(auto fn : plugins.get_importers()) c_importers.push_back(fn); - for(auto fn : plugins.get_functions()) c_functions.push_back(fn); - - // sort the items by priority (lowest first) - sort (c_headers.begin(), c_headers.end(), sort_importers); - sort (c_importers.begin(), c_importers.end(), sort_importers); - - emitter.set_filename(abs2rel(output_path, source_map_file, CWD)); - - } - - void Context::add_c_function(Sass_Function_Entry function) - { - c_functions.push_back(function); - } - void Context::add_c_header(Sass_Importer_Entry header) - { - c_headers.push_back(header); - // need to sort the array afterwards (no big deal) - sort (c_headers.begin(), c_headers.end(), sort_importers); - } - void Context::add_c_importer(Sass_Importer_Entry importer) - { - c_importers.push_back(importer); - // need to sort the array afterwards (no big deal) - sort (c_importers.begin(), c_importers.end(), sort_importers); - } - - Context::~Context() - { - // resources were allocated by malloc - for (size_t i = 0; i < resources.size(); ++i) { - free(resources[i].contents); - free(resources[i].srcmap); - } - // free all strings we kept alive during compiler execution - for (size_t n = 0; n < strings.size(); ++n) free(strings[n]); - // everything that gets put into sources will be freed by us - // this shouldn't have anything in it anyway!? - for (size_t m = 0; m < import_stack.size(); ++m) { - sass_import_take_source(import_stack[m]); - sass_import_take_srcmap(import_stack[m]); - sass_delete_import(import_stack[m]); - } - // clear inner structures (vectors) and input source - resources.clear(); import_stack.clear(); - sheets.clear(); - } - - Data_Context::~Data_Context() - { - // --> this will be freed by resources - // make sure we free the source even if not processed! - // if (resources.size() == 0 && source_c_str) free(source_c_str); - // if (resources.size() == 0 && srcmap_c_str) free(srcmap_c_str); - // source_c_str = 0; srcmap_c_str = 0; - } - - File_Context::~File_Context() - { - } - - void Context::collect_include_paths(const char* paths_str) - { - if (paths_str) { - const char* beg = paths_str; - const char* end = Prelexer::find_first(beg); - - while (end) { - sass::string path(beg, end - beg); - if (!path.empty()) { - if (*path.rbegin() != '/') path += '/'; - include_paths.push_back(path); - } - beg = end + 1; - end = Prelexer::find_first(beg); - } - - sass::string path(beg); - if (!path.empty()) { - if (*path.rbegin() != '/') path += '/'; - include_paths.push_back(path); - } - } - } - - void Context::collect_include_paths(string_list* paths_array) - { - while (paths_array) - { - collect_include_paths(paths_array->string); - paths_array = paths_array->next; - } - } - - void Context::collect_plugin_paths(const char* paths_str) - { - if (paths_str) { - const char* beg = paths_str; - const char* end = Prelexer::find_first(beg); - - while (end) { - sass::string path(beg, end - beg); - if (!path.empty()) { - if (*path.rbegin() != '/') path += '/'; - plugin_paths.push_back(path); - } - beg = end + 1; - end = Prelexer::find_first(beg); - } - - sass::string path(beg); - if (!path.empty()) { - if (*path.rbegin() != '/') path += '/'; - plugin_paths.push_back(path); - } - } - } - - void Context::collect_plugin_paths(string_list* paths_array) - { - while (paths_array) - { - collect_plugin_paths(paths_array->string); - paths_array = paths_array->next; - } - } - - // resolve the imp_path in base_path or include_paths - // looks for alternatives and returns a list from one directory - sass::vector Context::find_includes(const Importer& import) - { - // make sure we resolve against an absolute path - sass::string base_path(rel2abs(import.base_path)); - // first try to resolve the load path relative to the base path - sass::vector vec(resolve_includes(base_path, import.imp_path)); - // then search in every include path (but only if nothing found yet) - for (size_t i = 0, S = include_paths.size(); vec.size() == 0 && i < S; ++i) - { - // call resolve_includes and individual base path and append all results - sass::vector resolved(resolve_includes(include_paths[i], import.imp_path)); - if (resolved.size()) vec.insert(vec.end(), resolved.begin(), resolved.end()); - } - // return vector - return vec; - } - - // register include with resolved path and its content - // memory of the resources will be freed by us on exit - void Context::register_resource(const Include& inc, const Resource& res) - { - - // do not parse same resource twice - // maybe raise an error in this case - // if (sheets.count(inc.abs_path)) { - // free(res.contents); free(res.srcmap); - // throw std::runtime_error("duplicate resource registered"); - // return; - // } - - // get index for this resource - size_t idx = resources.size(); - - // tell emitter about new resource - emitter.add_source_index(idx); - - // put resources under our control - // the memory will be freed later - resources.push_back(res); - - // add a relative link to the working directory - included_files.push_back(inc.abs_path); - // add a relative link to the source map output file - srcmap_links.push_back(abs2rel(inc.abs_path, source_map_file, CWD)); - - // get pointer to the loaded content - Sass_Import_Entry import = sass_make_import( - inc.imp_path.c_str(), - inc.abs_path.c_str(), - res.contents, - res.srcmap - ); - // add the entry to the stack - import_stack.push_back(import); - - // get pointer to the loaded content - const char* contents = resources[idx].contents; - SourceFileObj source = SASS_MEMORY_NEW(SourceFile, - inc.abs_path.c_str(), contents, idx); - - // create the initial parser state from resource - SourceSpan pstate(source); - - // check existing import stack for possible recursion - for (size_t i = 0; i < import_stack.size() - 2; ++i) { - auto parent = import_stack[i]; - if (std::strcmp(parent->abs_path, import->abs_path) == 0) { - sass::string cwd(File::get_cwd()); - // make path relative to the current directory - sass::string stack("An @import loop has been found:"); - for (size_t n = 1; n < i + 2; ++n) { - stack += "\n " + sass::string(File::abs2rel(import_stack[n]->abs_path, cwd, cwd)) + - " imports " + sass::string(File::abs2rel(import_stack[n+1]->abs_path, cwd, cwd)); - } - // implement error throw directly until we - // decided how to handle full stack traces - throw Exception::InvalidSyntax(pstate, traces, stack); - // error(stack, prstate ? *prstate : pstate, import_stack); - } - } - - // create a parser instance from the given c_str buffer - Parser p(source, *this, traces); - // do not yet dispose these buffers - sass_import_take_source(import); - sass_import_take_srcmap(import); - // then parse the root block - Block_Obj root = p.parse(); - // delete memory of current stack frame - sass_delete_import(import_stack.back()); - // remove current stack frame - import_stack.pop_back(); - // create key/value pair for ast node - std::pair - ast_pair(inc.abs_path, { res, root }); - // register resulting resource - sheets.insert(ast_pair); - } - - // register include with resolved path and its content - // memory of the resources will be freed by us on exit - void Context::register_resource(const Include& inc, const Resource& res, SourceSpan& prstate) - { - traces.push_back(Backtrace(prstate)); - register_resource(inc, res); - traces.pop_back(); - } - - // Add a new import to the context (called from `import_url`) - Include Context::load_import(const Importer& imp, SourceSpan pstate) - { - - // search for valid imports (ie. partials) on the filesystem - // this may return more than one valid result (ambiguous imp_path) - const sass::vector resolved(find_includes(imp)); - - // error nicely on ambiguous imp_path - if (resolved.size() > 1) { - sass::ostream msg_stream; - msg_stream << "It's not clear which file to import for "; - msg_stream << "'@import \"" << imp.imp_path << "\"'." << "\n"; - msg_stream << "Candidates:" << "\n"; - for (size_t i = 0, L = resolved.size(); i < L; ++i) - { msg_stream << " " << resolved[i].imp_path << "\n"; } - msg_stream << "Please delete or rename all but one of these files." << "\n"; - error(msg_stream.str(), pstate, traces); - } - - // process the resolved entry - else if (resolved.size() == 1) { - bool use_cache = c_importers.size() == 0; - // use cache for the resource loading - if (use_cache && sheets.count(resolved[0].abs_path)) return resolved[0]; - // try to read the content of the resolved file entry - // the memory buffer returned must be freed by us! - if (char* contents = read_file(resolved[0].abs_path)) { - // register the newly resolved file resource - register_resource(resolved[0], { contents, 0 }, pstate); - // return resolved entry - return resolved[0]; - } - } - - // nothing found - return { imp, "" }; - - } - - void Context::import_url (Import* imp, sass::string load_path, const sass::string& ctx_path) { - - SourceSpan pstate(imp->pstate()); - sass::string imp_path(unquote(load_path)); - sass::string protocol("file"); - - using namespace Prelexer; - if (const char* proto = sequence< identifier, exactly<':'>, exactly<'/'>, exactly<'/'> >(imp_path.c_str())) { - - protocol = sass::string(imp_path.c_str(), proto - 3); - // if (protocol.compare("file") && true) { } - } - - // add urls (protocol other than file) and urls without protocol to `urls` member - // ToDo: if ctx_path is already a file resource, we should not add it here? - if (imp->import_queries() || protocol != "file" || imp_path.substr(0, 2) == "//") { - imp->urls().push_back(SASS_MEMORY_NEW(String_Quoted, imp->pstate(), load_path)); - } - else if (imp_path.length() > 4 && imp_path.substr(imp_path.length() - 4, 4) == ".css") { - String_Constant* loc = SASS_MEMORY_NEW(String_Constant, pstate, unquote(load_path)); - Argument_Obj loc_arg = SASS_MEMORY_NEW(Argument, pstate, loc); - Arguments_Obj loc_args = SASS_MEMORY_NEW(Arguments, pstate); - loc_args->append(loc_arg); - Function_Call* new_url = SASS_MEMORY_NEW(Function_Call, pstate, sass::string("url"), loc_args); - imp->urls().push_back(new_url); - } - else { - const Importer importer(imp_path, ctx_path); - Include include(load_import(importer, pstate)); - if (include.abs_path.empty()) { - error("File to import not found or unreadable: " + imp_path + ".", pstate, traces); - } - imp->incs().push_back(include); - } - - } - - - // call custom importers on the given (unquoted) load_path and eventually parse the resulting style_sheet - bool Context::call_loader(const sass::string& load_path, const char* ctx_path, SourceSpan& pstate, Import* imp, sass::vector importers, bool only_one) - { - // unique counter - size_t count = 0; - // need one correct import - bool has_import = false; - // process all custom importers (or custom headers) - for (Sass_Importer_Entry& importer_ent : importers) { - // int priority = sass_importer_get_priority(importer); - Sass_Importer_Fn fn = sass_importer_get_function(importer_ent); - // skip importer if it returns NULL - if (Sass_Import_List includes = - fn(load_path.c_str(), importer_ent, c_compiler) - ) { - // get c pointer copy to iterate over - Sass_Import_List it_includes = includes; - while (*it_includes) { ++count; - // create unique path to use as key - sass::string uniq_path = load_path; - if (!only_one && count) { - sass::ostream path_strm; - path_strm << uniq_path << ":" << count; - uniq_path = path_strm.str(); - } - // create the importer struct - Importer importer(uniq_path, ctx_path); - // query data from the current include - Sass_Import_Entry include_ent = *it_includes; - char* source = sass_import_take_source(include_ent); - char* srcmap = sass_import_take_srcmap(include_ent); - size_t line = sass_import_get_error_line(include_ent); - size_t column = sass_import_get_error_column(include_ent); - const char *abs_path = sass_import_get_abs_path(include_ent); - // handle error message passed back from custom importer - // it may (or may not) override the line and column info - if (const char* err_message = sass_import_get_error_message(include_ent)) { - if (source || srcmap) register_resource({ importer, uniq_path }, { source, srcmap }, pstate); - if (line == sass::string::npos && column == sass::string::npos) error(err_message, pstate, traces); - else { error(err_message, { pstate.source, { line, column } }, traces); } - } - // content for import was set - else if (source) { - // resolved abs_path should be set by custom importer - // use the created uniq_path as fallback (maybe enforce) - sass::string path_key(abs_path ? abs_path : uniq_path); - // create the importer struct - Include include(importer, path_key); - // attach information to AST node - imp->incs().push_back(include); - // register the resource buffers - register_resource(include, { source, srcmap }, pstate); - } - // only a path was retuned - // try to load it like normal - else if(abs_path) { - // checks some urls to preserve - // `http://`, `https://` and `//` - // or dispatchs to `import_file` - // which will check for a `.css` extension - // or resolves the file on the filesystem - // added and resolved via `add_file` - // finally stores everything on `imp` - import_url(imp, abs_path, ctx_path); - } - // move to next - ++it_includes; - } - // deallocate the returned memory - sass_delete_import_list(includes); - // set success flag - has_import = true; - // break out of loop - if (only_one) break; - } - } - // return result - return has_import; - } - - void register_function(Context&, Signature sig, Native_Function f, Env* env); - void register_function(Context&, Signature sig, Native_Function f, size_t arity, Env* env); - void register_overload_stub(Context&, sass::string name, Env* env); - void register_built_in_functions(Context&, Env* env); - void register_c_functions(Context&, Env* env, Sass_Function_List); - void register_c_function(Context&, Env* env, Sass_Function_Entry); - - char* Context::render(Block_Obj root) - { - // check for valid block - if (!root) return 0; - // start the render process - root->perform(&emitter); - // finish emitter stream - emitter.finalize(); - // get the resulting buffer from stream - OutputBuffer emitted = emitter.get_buffer(); - // should we append a source map url? - if (!c_options.omit_source_map_url) { - // generate an embedded source map - if (c_options.source_map_embed) { - emitted.buffer += linefeed; - emitted.buffer += format_embedded_source_map(); - } - // or just link the generated one - else if (source_map_file != "") { - emitted.buffer += linefeed; - emitted.buffer += format_source_mapping_url(source_map_file); - } - } - // create a copy of the resulting buffer string - // this must be freed or taken over by implementor - return sass_copy_c_string(emitted.buffer.c_str()); - } - - void Context::apply_custom_headers(Block_Obj root, const char* ctx_path, SourceSpan pstate) - { - // create a custom import to resolve headers - Import_Obj imp = SASS_MEMORY_NEW(Import, pstate); - // dispatch headers which will add custom functions - // custom headers are added to the import instance - call_headers(entry_path, ctx_path, pstate, imp); - // increase head count to skip later - head_imports += resources.size() - 1; - // add the statement if we have urls - if (!imp->urls().empty()) root->append(imp); - // process all other resources (add Import_Stub nodes) - for (size_t i = 0, S = imp->incs().size(); i < S; ++i) { - root->append(SASS_MEMORY_NEW(Import_Stub, pstate, imp->incs()[i])); - } - } - - Block_Obj File_Context::parse() - { - - // check if entry file is given - if (input_path.empty()) return {}; - - // create absolute path from input filename - // ToDo: this should be resolved via custom importers - sass::string abs_path(rel2abs(input_path, CWD)); - - // try to load the entry file - char* contents = read_file(abs_path); - - // alternatively also look inside each include path folder - // I think this differs from ruby sass (IMO too late to remove) - for (size_t i = 0, S = include_paths.size(); contents == 0 && i < S; ++i) { - // build absolute path for this include path entry - abs_path = rel2abs(input_path, include_paths[i]); - // try to load the resulting path - contents = read_file(abs_path); - } - - // abort early if no content could be loaded (various reasons) - if (!contents) throw std::runtime_error( - "File to read not found or unreadable: " - + std::string(input_path.c_str())); - - // store entry path - entry_path = abs_path; - - // create entry only for import stack - Sass_Import_Entry import = sass_make_import( - input_path.c_str(), - entry_path.c_str(), - contents, - 0 - ); - // add the entry to the stack - import_stack.push_back(import); - - // create the source entry for file entry - register_resource({{ input_path, "." }, abs_path }, { contents, 0 }); - - // create root ast tree node - return compile(); - - } - - Block_Obj Data_Context::parse() - { - - // check if source string is given - if (!source_c_str) return {}; - - // convert indented sass syntax - if(c_options.is_indented_syntax_src) { - // call sass2scss to convert the string - char * converted = sass2scss(source_c_str, - // preserve the structure as much as possible - SASS2SCSS_PRETTIFY_1 | SASS2SCSS_KEEP_COMMENT); - // replace old source_c_str with converted - free(source_c_str); source_c_str = converted; - } - - // remember entry path (defaults to stdin for string) - entry_path = input_path.empty() ? "stdin" : input_path; - - // ToDo: this may be resolved via custom importers - sass::string abs_path(rel2abs(entry_path)); - char* abs_path_c_str = sass_copy_c_string(abs_path.c_str()); - strings.push_back(abs_path_c_str); - - // create entry only for the import stack - Sass_Import_Entry import = sass_make_import( - entry_path.c_str(), - abs_path_c_str, - source_c_str, - srcmap_c_str - ); - // add the entry to the stack - import_stack.push_back(import); - - // register a synthetic resource (path does not really exist, skip in includes) - register_resource({{ input_path, "." }, input_path }, { source_c_str, srcmap_c_str }); - - // create root ast tree node - return compile(); - } - - // parse root block from includes - Block_Obj Context::compile() - { - // abort if there is no data - if (resources.size() == 0) return {}; - // get root block from the first style sheet - Block_Obj root = sheets.at(entry_path).root; - // abort on invalid root - if (root.isNull()) return {}; - Env global; // create root environment - // register built-in functions on env - register_built_in_functions(*this, &global); - // register custom functions (defined via C-API) - for (size_t i = 0, S = c_functions.size(); i < S; ++i) - { register_c_function(*this, &global, c_functions[i]); } - // create initial backtrace entry - // create crtp visitor objects - Expand expand(*this, &global); - Cssize cssize(*this); - CheckNesting check_nesting; - // check nesting in all files - for (auto sheet : sheets) { - auto styles = sheet.second; - check_nesting(styles.root); - } - // expand and eval the tree - root = expand(root); - - Extension unsatisfied; - // check that all extends were used - if (extender.checkForUnsatisfiedExtends(unsatisfied)) { - throw Exception::UnsatisfiedExtend(traces, unsatisfied); - } - - // check nesting - check_nesting(root); - // merge and bubble certain rules - root = cssize(root); - - // clean up by removing empty placeholders - // ToDo: maybe we can do this somewhere else? - Remove_Placeholders remove_placeholders; - root->perform(&remove_placeholders); - - // return processed tree - return root; - } - // EO compile - - sass::string Context::format_embedded_source_map() - { - sass::string map = emitter.render_srcmap(*this); - sass::istream is( map.c_str() ); - sass::ostream buffer; - base64::encoder E; - E.encode(is, buffer); - sass::string url = "data:application/json;base64," + buffer.str(); - url.erase(url.size() - 1); - return "/*# sourceMappingURL=" + url + " */"; - } - - sass::string Context::format_source_mapping_url(const sass::string& file) - { - sass::string url = abs2rel(file, output_path, CWD); - return "/*# sourceMappingURL=" + url + " */"; - } - - char* Context::render_srcmap() - { - if (source_map_file == "") return 0; - sass::string map = emitter.render_srcmap(*this); - return sass_copy_c_string(map.c_str()); - } - - - // for data context we want to start after "stdin" - // we probably always want to skip the header includes? - sass::vector Context::get_included_files(bool skip, size_t headers) - { - // create a copy of the vector for manipulations - sass::vector includes = included_files; - if (includes.size() == 0) return includes; - if (skip) { includes.erase( includes.begin(), includes.begin() + 1 + headers); } - else { includes.erase( includes.begin() + 1, includes.begin() + 1 + headers); } - includes.erase( std::unique( includes.begin(), includes.end() ), includes.end() ); - std::sort( includes.begin() + (skip ? 0 : 1), includes.end() ); - return includes; - } - - void register_function(Context& ctx, Signature sig, Native_Function f, Env* env) - { - Definition* def = make_native_function(sig, f, ctx); - def->environment(env); - (*env)[def->name() + "[f]"] = def; - } - - void register_function(Context& ctx, Signature sig, Native_Function f, size_t arity, Env* env) - { - Definition* def = make_native_function(sig, f, ctx); - sass::ostream ss; - ss << def->name() << "[f]" << arity; - def->environment(env); - (*env)[ss.str()] = def; - } - - void register_overload_stub(Context& ctx, sass::string name, Env* env) - { - Definition* stub = SASS_MEMORY_NEW(Definition, - SourceSpan{ "[built-in function]" }, - nullptr, - name, - Parameters_Obj{}, - nullptr, - true); - (*env)[name + "[f]"] = stub; - } - - - void register_built_in_functions(Context& ctx, Env* env) - { - using namespace Functions; - // RGB Functions - register_function(ctx, rgb_sig, rgb, env); - register_overload_stub(ctx, "rgba", env); - register_function(ctx, rgba_4_sig, rgba_4, 4, env); - register_function(ctx, rgba_2_sig, rgba_2, 2, env); - register_function(ctx, red_sig, red, env); - register_function(ctx, green_sig, green, env); - register_function(ctx, blue_sig, blue, env); - register_function(ctx, mix_sig, mix, env); - // HSL Functions - register_function(ctx, hsl_sig, hsl, env); - register_function(ctx, hsla_sig, hsla, env); - register_function(ctx, hue_sig, hue, env); - register_function(ctx, saturation_sig, saturation, env); - register_function(ctx, lightness_sig, lightness, env); - register_function(ctx, adjust_hue_sig, adjust_hue, env); - register_function(ctx, lighten_sig, lighten, env); - register_function(ctx, darken_sig, darken, env); - register_function(ctx, saturate_sig, saturate, env); - register_function(ctx, desaturate_sig, desaturate, env); - register_function(ctx, grayscale_sig, grayscale, env); - register_function(ctx, complement_sig, complement, env); - register_function(ctx, invert_sig, invert, env); - // Opacity Functions - register_function(ctx, alpha_sig, alpha, env); - register_function(ctx, opacity_sig, alpha, env); - register_function(ctx, opacify_sig, opacify, env); - register_function(ctx, fade_in_sig, opacify, env); - register_function(ctx, transparentize_sig, transparentize, env); - register_function(ctx, fade_out_sig, transparentize, env); - // Other Color Functions - register_function(ctx, adjust_color_sig, adjust_color, env); - register_function(ctx, scale_color_sig, scale_color, env); - register_function(ctx, change_color_sig, change_color, env); - register_function(ctx, ie_hex_str_sig, ie_hex_str, env); - // String Functions - register_function(ctx, unquote_sig, sass_unquote, env); - register_function(ctx, quote_sig, sass_quote, env); - register_function(ctx, str_length_sig, str_length, env); - register_function(ctx, str_insert_sig, str_insert, env); - register_function(ctx, str_index_sig, str_index, env); - register_function(ctx, str_slice_sig, str_slice, env); - register_function(ctx, to_upper_case_sig, to_upper_case, env); - register_function(ctx, to_lower_case_sig, to_lower_case, env); - // Number Functions - register_function(ctx, percentage_sig, percentage, env); - register_function(ctx, round_sig, round, env); - register_function(ctx, ceil_sig, ceil, env); - register_function(ctx, floor_sig, floor, env); - register_function(ctx, abs_sig, abs, env); - register_function(ctx, min_sig, min, env); - register_function(ctx, max_sig, max, env); - register_function(ctx, random_sig, random, env); - // List Functions - register_function(ctx, length_sig, length, env); - register_function(ctx, nth_sig, nth, env); - register_function(ctx, set_nth_sig, set_nth, env); - register_function(ctx, index_sig, index, env); - register_function(ctx, join_sig, join, env); - register_function(ctx, append_sig, append, env); - register_function(ctx, zip_sig, zip, env); - register_function(ctx, list_separator_sig, list_separator, env); - register_function(ctx, is_bracketed_sig, is_bracketed, env); - // Map Functions - register_function(ctx, map_get_sig, map_get, env); - register_function(ctx, map_merge_sig, map_merge, env); - register_function(ctx, map_remove_sig, map_remove, env); - register_function(ctx, map_keys_sig, map_keys, env); - register_function(ctx, map_values_sig, map_values, env); - register_function(ctx, map_has_key_sig, map_has_key, env); - register_function(ctx, keywords_sig, keywords, env); - // Introspection Functions - register_function(ctx, type_of_sig, type_of, env); - register_function(ctx, unit_sig, unit, env); - register_function(ctx, unitless_sig, unitless, env); - register_function(ctx, comparable_sig, comparable, env); - register_function(ctx, variable_exists_sig, variable_exists, env); - register_function(ctx, global_variable_exists_sig, global_variable_exists, env); - register_function(ctx, function_exists_sig, function_exists, env); - register_function(ctx, mixin_exists_sig, mixin_exists, env); - register_function(ctx, feature_exists_sig, feature_exists, env); - register_function(ctx, call_sig, call, env); - register_function(ctx, content_exists_sig, content_exists, env); - register_function(ctx, get_function_sig, get_function, env); - // Boolean Functions - register_function(ctx, not_sig, sass_not, env); - register_function(ctx, if_sig, sass_if, env); - // Misc Functions - register_function(ctx, inspect_sig, inspect, env); - register_function(ctx, unique_id_sig, unique_id, env); - // Selector functions - register_function(ctx, selector_nest_sig, selector_nest, env); - register_function(ctx, selector_append_sig, selector_append, env); - register_function(ctx, selector_extend_sig, selector_extend, env); - register_function(ctx, selector_replace_sig, selector_replace, env); - register_function(ctx, selector_unify_sig, selector_unify, env); - register_function(ctx, is_superselector_sig, is_superselector, env); - register_function(ctx, simple_selectors_sig, simple_selectors, env); - register_function(ctx, selector_parse_sig, selector_parse, env); - } - - void register_c_functions(Context& ctx, Env* env, Sass_Function_List descrs) - { - while (descrs && *descrs) { - register_c_function(ctx, env, *descrs); - ++descrs; - } - } - void register_c_function(Context& ctx, Env* env, Sass_Function_Entry descr) - { - Definition* def = make_c_function(descr, ctx); - def->environment(env); - (*env)[def->name() + "[f]"] = def; - } - -} diff --git a/src/context.hpp b/src/context.hpp deleted file mode 100644 index 29c5fc7d45..0000000000 --- a/src/context.hpp +++ /dev/null @@ -1,140 +0,0 @@ -#ifndef SASS_CONTEXT_H -#define SASS_CONTEXT_H - -// sass.hpp must go before all system headers to get the -// __EXTENSIONS__ fix on Solaris. -#include "sass.hpp" -#include "ast.hpp" - - -#define BUFFERSIZE 255 -#include "b64/encode.h" - -#include "sass_context.hpp" -#include "stylesheet.hpp" -#include "plugins.hpp" -#include "output.hpp" - -namespace Sass { - - class Context { - public: - void import_url (Import* imp, sass::string load_path, const sass::string& ctx_path); - bool call_headers(const sass::string& load_path, const char* ctx_path, SourceSpan& pstate, Import* imp) - { return call_loader(load_path, ctx_path, pstate, imp, c_headers, false); }; - bool call_importers(const sass::string& load_path, const char* ctx_path, SourceSpan& pstate, Import* imp) - { return call_loader(load_path, ctx_path, pstate, imp, c_importers, true); }; - - private: - bool call_loader(const sass::string& load_path, const char* ctx_path, SourceSpan& pstate, Import* imp, sass::vector importers, bool only_one = true); - - public: - const sass::string CWD; - struct Sass_Options& c_options; - sass::string entry_path; - size_t head_imports; - Plugins plugins; - Output emitter; - - // generic ast node garbage container - // used to avoid possible circular refs - CallStack ast_gc; - // resources add under our control - // these are guaranteed to be freed - sass::vector strings; - sass::vector resources; - std::map sheets; - ImporterStack import_stack; - sass::vector callee_stack; - sass::vector traces; - Extender extender; - - struct Sass_Compiler* c_compiler; - - // absolute paths to includes - sass::vector included_files; - // relative includes for sourcemap - sass::vector srcmap_links; - // vectors above have same size - - sass::vector plugin_paths; // relative paths to load plugins - sass::vector include_paths; // lookup paths for includes - - void apply_custom_headers(Block_Obj root, const char* path, SourceSpan pstate); - - sass::vector c_headers; - sass::vector c_importers; - sass::vector c_functions; - - void add_c_header(Sass_Importer_Entry header); - void add_c_importer(Sass_Importer_Entry importer); - void add_c_function(Sass_Function_Entry function); - - const sass::string indent; // String to be used for indentation - const sass::string linefeed; // String to be used for line feeds - const sass::string input_path; // for relative paths in src-map - const sass::string output_path; // for relative paths to the output - const sass::string source_map_file; // path to source map file (enables feature) - const sass::string source_map_root; // path for sourceRoot property (pass-through) - - virtual ~Context(); - Context(struct Sass_Context&); - virtual Block_Obj parse() = 0; - virtual Block_Obj compile(); - virtual char* render(Block_Obj root); - virtual char* render_srcmap(); - - void register_resource(const Include&, const Resource&); - void register_resource(const Include&, const Resource&, SourceSpan&); - sass::vector find_includes(const Importer& import); - Include load_import(const Importer&, SourceSpan pstate); - - Sass_Output_Style output_style() { return c_options.output_style; }; - sass::vector get_included_files(bool skip = false, size_t headers = 0); - - private: - void collect_plugin_paths(const char* paths_str); - void collect_plugin_paths(string_list* paths_array); - void collect_include_paths(const char* paths_str); - void collect_include_paths(string_list* paths_array); - sass::string format_embedded_source_map(); - sass::string format_source_mapping_url(const sass::string& out_path); - - - // void register_built_in_functions(Env* env); - // void register_function(Signature sig, Native_Function f, Env* env); - // void register_function(Signature sig, Native_Function f, size_t arity, Env* env); - // void register_overload_stub(sass::string name, Env* env); - - public: - const sass::string& cwd() { return CWD; }; - }; - - class File_Context : public Context { - public: - File_Context(struct Sass_File_Context& ctx) - : Context(ctx) - { } - virtual ~File_Context(); - virtual Block_Obj parse(); - }; - - class Data_Context : public Context { - public: - char* source_c_str; - char* srcmap_c_str; - Data_Context(struct Sass_Data_Context& ctx) - : Context(ctx) - { - source_c_str = ctx.source_string; - srcmap_c_str = ctx.srcmap_string; - ctx.source_string = 0; // passed away - ctx.srcmap_string = 0; // passed away - } - virtual ~Data_Context(); - virtual Block_Obj parse(); - }; - -} - -#endif diff --git a/src/css_every.cpp b/src/css_every.cpp new file mode 100644 index 0000000000..6ae51e89b2 --- /dev/null +++ b/src/css_every.cpp @@ -0,0 +1,77 @@ +#include "css_every.hpp" + +#include "ast_css.hpp" + +namespace Sass { + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + bool Sass::EveryCssVisitor::visitCssAtRule(CssAtRule* css) + { + for (auto& child : css->elements()) { + if (!child->accept(this)) return false; + } + return true; + } + + bool EveryCssVisitor::visitCssComment(CssComment* css) + { + return false; + } + + bool EveryCssVisitor::visitCssDeclaration(CssDeclaration* css) + { + return false; + } + + bool EveryCssVisitor::visitCssImport(CssImport* css) + { + return false; + } + + bool EveryCssVisitor::visitCssKeyframeBlock(CssKeyframeBlock* css) + { + for (auto& child : css->elements()) { + if (!child->accept(this)) return false; + } + return true; + } + + bool EveryCssVisitor::visitCssMediaRule(CssMediaRule* css) + { + for (auto& child : css->elements()) { + if (!child->accept(this)) return false; + } + return true; + } + + bool EveryCssVisitor::visitCssRoot(CssRoot* css) + { + for (auto& child : css->elements()) { + if (!child->accept(this)) return false; + } + return true; + } + + bool EveryCssVisitor::visitCssStyleRule(CssStyleRule* css) + { + for (auto& child : css->elements()) { + if (!child->accept(this)) return false; + } + return true; + } + + bool EveryCssVisitor::visitCssSupportsRule(CssSupportsRule* css) + { + for (auto& child : css->elements()) { + if (!child->accept(this)) return false; + } + return true; + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + +} + diff --git a/src/css_every.hpp b/src/css_every.hpp new file mode 100644 index 0000000000..1a907852a9 --- /dev/null +++ b/src/css_every.hpp @@ -0,0 +1,35 @@ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#ifndef SASS_CSS_EVERY_HPP +#define SASS_CSS_EVERY_HPP + +#include "visitor_css.hpp" + +namespace Sass { + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + class EveryCssVisitor : public CssVisitor { + + public: + + virtual bool visitCssAtRule(CssAtRule* css); + virtual bool visitCssComment(CssComment* css); + virtual bool visitCssDeclaration(CssDeclaration* css); + virtual bool visitCssImport(CssImport* css); + virtual bool visitCssKeyframeBlock(CssKeyframeBlock* css); + virtual bool visitCssMediaRule(CssMediaRule* css); + virtual bool visitCssRoot(CssRoot* css); + virtual bool visitCssStyleRule(CssStyleRule* css); + virtual bool visitCssSupportsRule(CssSupportsRule* css); + + }; + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + +} + +#endif diff --git a/src/css_invisible.cpp b/src/css_invisible.cpp new file mode 100644 index 0000000000..0580608dd1 --- /dev/null +++ b/src/css_invisible.cpp @@ -0,0 +1,47 @@ +#include "css_invisible.hpp" + +#include "ast_css.hpp" + +namespace Sass { + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + IsCssInvisibleVisitor::IsCssInvisibleVisitor( + bool includeBogus, bool includeComments) : + includeBogus(includeBogus), + includeComments(includeComments) + {} + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + bool IsCssInvisibleVisitor::visitCssAtRule(CssAtRule * rule) + { + return false; + } + + bool IsCssInvisibleVisitor::visitCssComment(CssComment* comment) + { + return includeComments && !comment->isPreserved(); + } + + bool IsCssInvisibleVisitor::visitCssStyleRule(CssStyleRule* rule) + { + //std::cerr << "visit css style rule " << includeBogus << "\n"; + if (includeBogus && rule->selector()->isInvisible()) { + //std::cerr << "has bogus\n"; + return true; + } + if (rule->selector()->isInvisibleOtherThanBogusCombinators()) { + //std::cerr << "has bogus 2\n"; + return true; + } + return EveryCssVisitor::visitCssStyleRule(rule); + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + +} + diff --git a/src/css_invisible.hpp b/src/css_invisible.hpp new file mode 100644 index 0000000000..1a24e97e0a --- /dev/null +++ b/src/css_invisible.hpp @@ -0,0 +1,37 @@ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#ifndef SASS_CSS_INVISIBLE_HPP +#define SASS_CSS_INVISIBLE_HPP + +#include "css_every.hpp" + +namespace Sass { + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + class IsCssInvisibleVisitor : public EveryCssVisitor { + + /// Whether to consider selectors with bogus combinators invisible. + bool includeBogus; + + /// Whether to consider comments invisible. + bool includeComments; + + public: + + IsCssInvisibleVisitor(bool includeBogus, bool includeComments); + + virtual bool visitCssAtRule(CssAtRule* rule) override final; + virtual bool visitCssComment(CssComment* rule) override final; + virtual bool visitCssStyleRule(CssStyleRule* rule) override final; + + }; + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + +} + +#endif diff --git a/src/cssize.cpp b/src/cssize.cpp index a651186eec..91047dba78 100644 --- a/src/cssize.cpp +++ b/src/cssize.cpp @@ -1,521 +1,72 @@ -// sass.hpp must go before all system headers to get the -// __EXTENSIONS__ fix on Solaris. -#include "sass.hpp" - -#include -#include -#include - #include "cssize.hpp" -#include "context.hpp" - -namespace Sass { - - Cssize::Cssize(Context& ctx) - : traces(ctx.traces), - block_stack(BlockStack()), - p_stack(sass::vector()) - { } - - Statement* Cssize::parent() - { - return p_stack.size() ? p_stack.back() : block_stack.front(); - } - - Block* Cssize::operator()(Block* b) - { - Block_Obj bb = SASS_MEMORY_NEW(Block, b->pstate(), b->length(), b->is_root()); - // bb->tabs(b->tabs()); - block_stack.push_back(bb); - append_block(b, bb); - block_stack.pop_back(); - return bb.detach(); - } - - Statement* Cssize::operator()(Trace* t) - { - traces.push_back(Backtrace(t->pstate())); - auto result = t->block()->perform(this); - traces.pop_back(); - return result; - } - - Statement* Cssize::operator()(Declaration* d) - { - String_Obj property = Cast(d->property()); - - if (Declaration* dd = Cast(parent())) { - String_Obj parent_property = Cast(dd->property()); - property = SASS_MEMORY_NEW(String_Constant, - d->property()->pstate(), - parent_property->to_string() + "-" + property->to_string()); - if (!dd->value()) { - d->tabs(dd->tabs() + 1); - } - } - - Declaration_Obj dd = SASS_MEMORY_NEW(Declaration, - d->pstate(), - property, - d->value(), - d->is_important(), - d->is_custom_property()); - dd->is_indented(d->is_indented()); - dd->tabs(d->tabs()); - - p_stack.push_back(dd); - Block_Obj bb = d->block() ? operator()(d->block()) : NULL; - p_stack.pop_back(); - - if (bb && bb->length()) { - if (dd->value() && !dd->value()->is_invisible()) { - bb->unshift(dd); - } - return bb.detach(); - } - else if (dd->value() && !dd->value()->is_invisible()) { - return dd.detach(); - } - - return 0; - } - - Statement* Cssize::operator()(AtRule* r) - { - if (!r->block() || !r->block()->length()) return r; - - if (parent()->statement_type() == Statement::RULESET) - { - return r->is_keyframes() ? SASS_MEMORY_NEW(Bubble, r->pstate(), r) : bubble(r); - } - - p_stack.push_back(r); - AtRuleObj rr = SASS_MEMORY_NEW(AtRule, - r->pstate(), - r->keyword(), - r->selector(), - r->block() ? operator()(r->block()) : 0); - if (r->value()) rr->value(r->value()); - p_stack.pop_back(); - - bool directive_exists = false; - size_t L = rr->block() ? rr->block()->length() : 0; - for (size_t i = 0; i < L && !directive_exists; ++i) { - Statement_Obj s = r->block()->at(i); - if (s->statement_type() != Statement::BUBBLE) directive_exists = true; - else { - Bubble_Obj s_obj = Cast(s); - s = s_obj->node(); - if (s->statement_type() != Statement::DIRECTIVE) directive_exists = false; - else directive_exists = (Cast(s)->keyword() == rr->keyword()); - } - } - - Block* result = SASS_MEMORY_NEW(Block, rr->pstate()); - if (!(directive_exists || rr->is_keyframes())) - { - AtRule* empty_node = Cast(rr); - empty_node->block(SASS_MEMORY_NEW(Block, rr->block() ? rr->block()->pstate() : rr->pstate())); - result->append(empty_node); - } +#include "charcode.hpp" +#include "character.hpp" +#include "ast_values.hpp" +#include "exceptions.hpp" - Block_Obj db = rr->block(); - if (db.isNull()) db = SASS_MEMORY_NEW(Block, rr->pstate()); - Block_Obj ss = debubble(db, rr); - for (size_t i = 0, L = ss->length(); i < L; ++i) { - result->append(ss->at(i)); - } +namespace Sass { - return result; - } + // Import some namespaces + using namespace Charcode; + using namespace Character; - Statement* Cssize::operator()(Keyframe_Rule* r) + void Cssize::visitFunction(Function* f) { - if (!r->block() || !r->block()->length()) return r; - - Keyframe_Rule_Obj rr = SASS_MEMORY_NEW(Keyframe_Rule, - r->pstate(), - operator()(r->block())); - if (!r->name().isNull()) rr->name(r->name()); - - return debubble(rr->block(), rr); + throw Exception::InvalidCssValue({}, *f); } - Statement* Cssize::operator()(StyleRule* r) + void Cssize::visitMap(Map* value) { - p_stack.push_back(r); - // this can return a string schema - // string schema is not a statement! - // r->block() is already a string schema - // and that is coming from propset expand - Block* bb = operator()(r->block()); - // this should protect us (at least a bit) from our mess - // fixing this properly is harder that it should be ... - if (Cast(bb) == NULL) { - error("Illegal nesting: Only properties may be nested beneath properties.", r->block()->pstate(), traces); + if (output_style() == SASS_STYLE_TO_CSS) { + // should be handled in check_expression + throw Exception::InvalidCssValue({}, *value); } - StyleRuleObj rr = SASS_MEMORY_NEW(StyleRule, - r->pstate(), - r->selector(), - bb); + if (value->empty()) return; - rr->is_root(r->is_root()); - // rr->tabs(r->block()->tabs()); - p_stack.pop_back(); - - if (!rr->block()) { - error("Illegal nesting: Only properties may be nested beneath properties.", r->block()->pstate(), traces); - } - - Block_Obj props = SASS_MEMORY_NEW(Block, rr->block()->pstate()); - Block* rules = SASS_MEMORY_NEW(Block, rr->block()->pstate()); - for (size_t i = 0, L = rr->block()->length(); i < L; i++) - { - Statement* s = rr->block()->at(i); - if (bubblable(s)) rules->append(s); - if (!bubblable(s)) props->append(s); - } - - if (props->length()) - { - Block_Obj pb = SASS_MEMORY_NEW(Block, rr->block()->pstate()); - pb->concat(props); - rr->block(pb); - - for (size_t i = 0, L = rules->length(); i < L; i++) - { - Statement* stm = rules->at(i); - stm->tabs(stm->tabs() + 1); - } - - rules->unshift(rr); - } - - Block* ptr = rules; - rules = debubble(rules); - void* lp = ptr; - void* rp = rules; - if (lp != rp) { - Block_Obj obj = ptr; - } - - if (!(!rules->length() || - !bubblable(rules->last()) || - parent()->statement_type() == Statement::RULESET)) - { - rules->last()->group_end(true); - } - return rules; + Inspect::visitMap(value); } - Statement* Cssize::operator()(Null* m) + void Cssize::visitList(List* list) { - return 0; - } - - Statement* Cssize::operator()(CssMediaRule* m) - { - if (parent()->statement_type() == Statement::RULESET) - { - return bubble(m); + if (list->empty() && !list->hasBrackets()) { + throw Exception::InvalidCssValue({}, *list); } - - if (parent()->statement_type() == Statement::MEDIA) - { - return SASS_MEMORY_NEW(Bubble, m->pstate(), m); - } - - p_stack.push_back(m); - - CssMediaRuleObj mm = SASS_MEMORY_NEW(CssMediaRule, m->pstate(), m->block()); - mm->concat(m->elements()); - mm->block(operator()(m->block())); - mm->tabs(m->tabs()); - - p_stack.pop_back(); - - return debubble(mm->block(), mm); - } - - Statement* Cssize::operator()(SupportsRule* m) - { - if (!m->block()->length()) - { return m; } - - if (parent()->statement_type() == Statement::RULESET) - { return bubble(m); } - - p_stack.push_back(m); - - SupportsRuleObj mm = SASS_MEMORY_NEW(SupportsRule, - m->pstate(), - m->condition(), - operator()(m->block())); - mm->tabs(m->tabs()); - - p_stack.pop_back(); - - return debubble(mm->block(), mm); + Inspect::visitList(list); } - Statement* Cssize::operator()(AtRootRule* m) + void Cssize::visitNumber(Number* n) { - bool tmp = false; - for (size_t i = 0, L = p_stack.size(); i < L; ++i) { - Statement* s = p_stack[i]; - tmp |= m->exclude_node(s); - } - - if (!tmp && m->block()) - { - Block* bb = operator()(m->block()); - for (size_t i = 0, L = bb->length(); i < L; ++i) { - // (bb->elements())[i]->tabs(m->tabs()); - Statement_Obj stm = bb->at(i); - if (bubblable(stm)) stm->tabs(stm->tabs() + m->tabs()); - } - if (bb->length() && bubblable(bb->last())) bb->last()->group_end(m->group_end()); - return bb; - } - if (m->exclude_node(parent())) - { - return SASS_MEMORY_NEW(Bubble, m->pstate(), m); + if (n->lhsAsSlash() && n->rhsAsSlash()) { + n->lhsAsSlash()->accept(this); + append_string("/"); + n->rhsAsSlash()->accept(this); + return; } - return bubble(m); - } - - Statement* Cssize::bubble(AtRule* m) - { - Block* bb = SASS_MEMORY_NEW(Block, this->parent()->pstate()); - ParentStatementObj new_rule = Cast(SASS_MEMORY_COPY(this->parent())); - new_rule->block(bb); - new_rule->tabs(this->parent()->tabs()); - new_rule->block()->concat(m->block()); - - Block_Obj wrapper_block = SASS_MEMORY_NEW(Block, m->block() ? m->block()->pstate() : m->pstate()); - wrapper_block->append(new_rule); - AtRuleObj mm = SASS_MEMORY_NEW(AtRule, - m->pstate(), - m->keyword(), - m->selector(), - wrapper_block); - if (m->value()) mm->value(m->value()); - - Bubble* bubble = SASS_MEMORY_NEW(Bubble, mm->pstate(), mm); - return bubble; - } + // reduce units + n->reduce(); - Statement* Cssize::bubble(AtRootRule* m) - { - if (!m || !m->block()) return NULL; - Block* bb = SASS_MEMORY_NEW(Block, this->parent()->pstate()); - ParentStatementObj new_rule = Cast(SASS_MEMORY_COPY(this->parent())); - Block* wrapper_block = SASS_MEMORY_NEW(Block, m->block()->pstate()); - if (new_rule) { - new_rule->block(bb); - new_rule->tabs(this->parent()->tabs()); - new_rule->block()->concat(m->block()); - wrapper_block->append(new_rule); + if (outopt.output_style == SASS_STYLE_TO_CSS && !n->isValidCssUnit()) { + // traces.push_back(BackTrace(nr->pstate())); + // issue_1804 + throw Exception::InvalidCssValue({}, *n); } - AtRootRule* mm = SASS_MEMORY_NEW(AtRootRule, - m->pstate(), - wrapper_block, - m->expression()); - Bubble* bubble = SASS_MEMORY_NEW(Bubble, mm->pstate(), mm); - return bubble; - } - - Statement* Cssize::bubble(SupportsRule* m) - { - StyleRuleObj parent = Cast(SASS_MEMORY_COPY(this->parent())); - - Block* bb = SASS_MEMORY_NEW(Block, parent->block()->pstate()); - StyleRule* new_rule = SASS_MEMORY_NEW(StyleRule, - parent->pstate(), - parent->selector(), - bb); - new_rule->tabs(parent->tabs()); - new_rule->block()->concat(m->block()); - - Block* wrapper_block = SASS_MEMORY_NEW(Block, m->block()->pstate()); - wrapper_block->append(new_rule); - SupportsRule* mm = SASS_MEMORY_NEW(SupportsRule, - m->pstate(), - m->condition(), - wrapper_block); - - mm->tabs(m->tabs()); - - Bubble* bubble = SASS_MEMORY_NEW(Bubble, mm->pstate(), mm); - return bubble; - } - - Statement* Cssize::bubble(CssMediaRule* m) - { - StyleRuleObj parent = Cast(SASS_MEMORY_COPY(this->parent())); - - Block* bb = SASS_MEMORY_NEW(Block, parent->block()->pstate()); - StyleRule* new_rule = SASS_MEMORY_NEW(StyleRule, - parent->pstate(), - parent->selector(), - bb); - new_rule->tabs(parent->tabs()); - new_rule->block()->concat(m->block()); - - Block* wrapper_block = SASS_MEMORY_NEW(Block, m->block()->pstate()); - wrapper_block->append(new_rule); - CssMediaRuleObj mm = SASS_MEMORY_NEW(CssMediaRule, - m->pstate(), - wrapper_block); - mm->concat(m->elements()); - - mm->tabs(m->tabs()); - - return SASS_MEMORY_NEW(Bubble, mm->pstate(), mm); - } - - bool Cssize::bubblable(Statement* s) - { - return Cast(s) || (s && s->bubbles()); - } - - Block* Cssize::flatten(const Block* b) - { - Block* result = SASS_MEMORY_NEW(Block, b->pstate(), 0, b->is_root()); - for (size_t i = 0, L = b->length(); i < L; ++i) { - Statement* ss = b->at(i); - if (const Block* bb = Cast(ss)) { - Block_Obj bs = flatten(bb); - for (size_t j = 0, K = bs->length(); j < K; ++j) { - result->append(bs->at(j)); - } - } - else { - result->append(ss); - } + if (n->isValidCssUnit()) { + Inspect::visitNumber(n); } - return result; - } - - sass::vector> Cssize::slice_by_bubble(Block* b) - { - sass::vector> results; - - for (size_t i = 0, L = b->length(); i < L; ++i) { - Statement_Obj value = b->at(i); - bool key = Cast(value) != NULL; - - if (!results.empty() && results.back().first == key) - { - Block_Obj wrapper_block = results.back().second; - wrapper_block->append(value); - } - else - { - Block* wrapper_block = SASS_MEMORY_NEW(Block, value->pstate()); - wrapper_block->append(value); - results.push_back(std::make_pair(key, wrapper_block)); - } + else if (!n->isValidCssUnit()) { + throw Exception::InvalidCssValue({}, *n); } - return results; - } - - Block* Cssize::debubble(Block* children, Statement* parent) - { - ParentStatementObj previous_parent; - sass::vector> baz = slice_by_bubble(children); - Block_Obj result = SASS_MEMORY_NEW(Block, children->pstate()); - - for (size_t i = 0, L = baz.size(); i < L; ++i) { - bool is_bubble = baz[i].first; - Block_Obj slice = baz[i].second; - - if (!is_bubble) { - if (!parent) { - result->append(slice); - } - else if (previous_parent) { - previous_parent->block()->concat(slice); - } - else { - previous_parent = SASS_MEMORY_COPY(parent); - previous_parent->block(slice); - previous_parent->tabs(parent->tabs()); - - result->append(previous_parent); - } - continue; - } - - for (size_t j = 0, K = slice->length(); j < K; ++j) - { - Statement_Obj ss; - Statement_Obj stm = slice->at(j); - // this has to go now here (too bad) - Bubble_Obj node = Cast(stm); - - CssMediaRule* rule1 = NULL; - CssMediaRule* rule2 = NULL; - if (parent) rule1 = Cast(parent); - if (node) rule2 = Cast(node->node()); - if (rule1 || rule2) { - ss = node->node(); - } - - ss = node->node(); - - if (!ss) { - continue; - } - - ss->tabs(ss->tabs() + node->tabs()); - ss->group_end(node->group_end()); - - Block_Obj bb = SASS_MEMORY_NEW(Block, - children->pstate(), - children->length(), - children->is_root()); - auto evaled = ss->perform(this); - if (evaled) bb->append(evaled); - - Block_Obj wrapper_block = SASS_MEMORY_NEW(Block, - children->pstate(), - children->length(), - children->is_root()); - - Block* wrapper = flatten(bb); - wrapper_block->append(wrapper); - - if (wrapper->length()) { - previous_parent = {}; - } - - if (wrapper_block) { - result->append(wrapper_block); - } - } + else { + //append_string("calc("); + Inspect::visitNumber(n); + //append_string(")"); } - return flatten(result); - } - - void Cssize::append_block(Block* b, Block* cur) - { - for (size_t i = 0, L = b->length(); i < L; ++i) { - Statement_Obj ith = b->at(i)->perform(this); - if (Block_Obj bb = Cast(ith)) { - for (size_t j = 0, K = bb->length(); j < K; ++j) { - cur->append(bb->at(j)); - } - } - else if (ith) { - cur->append(ith); - } - } } } + diff --git a/src/cssize.hpp b/src/cssize.hpp index 4cb13d79c4..d49df4e157 100644 --- a/src/cssize.hpp +++ b/src/cssize.hpp @@ -1,69 +1,35 @@ -#ifndef SASS_CSSIZE_H -#define SASS_CSSIZE_H +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#ifndef SASS_CSSIZE_HPP +#define SASS_CSSIZE_HPP -#include "ast.hpp" -#include "context.hpp" -#include "operation.hpp" -#include "environment.hpp" +#include "inspect.hpp" namespace Sass { - struct Backtrace; - - class Cssize : public Operation_CRTP { - - Backtraces& traces; - BlockStack block_stack; - sass::vector p_stack; - + class Cssize : public Inspect { public: - Cssize(Context&); - ~Cssize() { } - Block* operator()(Block*); - Statement* operator()(StyleRule*); - // Statement* operator()(Bubble*); - Statement* operator()(CssMediaRule*); - Statement* operator()(SupportsRule*); - Statement* operator()(AtRootRule*); - Statement* operator()(AtRule*); - Statement* operator()(Keyframe_Rule*); - Statement* operator()(Trace*); - Statement* operator()(Declaration*); - // Statement* operator()(Assignment*); - // Statement* operator()(Import*); - // Statement* operator()(Import_Stub*); - // Statement* operator()(WarningRule*); - // Statement* operator()(Error*); - // Statement* operator()(Comment*); - // Statement* operator()(If*); - // Statement* operator()(ForRule*); - // Statement* operator()(EachRule*); - // Statement* operator()(WhileRule*); - // Statement* operator()(Return*); - // Statement* operator()(ExtendRule*); - // Statement* operator()(Definition*); - // Statement* operator()(Mixin_Call*); - // Statement* operator()(Content*); - Statement* operator()(Null*); + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + Cssize( + OutputOptions& opt) : + Inspect(opt) + {} - Statement* parent(); - sass::vector> slice_by_bubble(Block*); - Statement* bubble(AtRule*); - Statement* bubble(AtRootRule*); - Statement* bubble(CssMediaRule*); - Statement* bubble(SupportsRule*); + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// - Block* debubble(Block* children, Statement* parent = 0); - Block* flatten(const Block*); - bool bubblable(Statement*); + virtual void visitFunction(Function*) override; + virtual void visitNumber(Number*) override; + virtual void visitList(List*) override; + virtual void visitMap(Map*) override; - // generic fallback - template - Statement* fallback(U x) - { return Cast(x); } + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// - void append_block(Block*, Block*); }; } diff --git a/src/dart_helpers.hpp b/src/dart_helpers.hpp index 28b79af610..8ffd304224 100644 --- a/src/dart_helpers.hpp +++ b/src/dart_helpers.hpp @@ -1,16 +1,77 @@ -#ifndef SASS_DART_HELPERS_H -#define SASS_DART_HELPERS_H +#ifndef SASS_DART_HELPERS_HPP +#define SASS_DART_HELPERS_HPP #include #include #include #include +#include "ast_helpers.hpp" namespace Sass { - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + template + sass::string InspectVector(sass::vector exts) { + sass::string msg = "["; + bool first = true; + for (auto& entry : exts) { + if (!first) msg += ", "; + msg += entry->inspect(); + first = false; + } + return msg + "]"; + + } + + template + sass::string InspectSelector(sass::vector exts) { + sass::string msg = "["; + bool first = true; + for (auto& entry : exts) { + if (!first) msg += ", "; + msg += entry.selector->inspect(); + first = false; + } + return msg + "]"; + + } + ///////////////////////////////////////////////////////////////////////// + // Returns a new list containing the elements between [start] and [end]. + ///////////////////////////////////////////////////////////////////////// + template + sass::vector sublist(const sass::vector& vec, + size_t start, size_t end = sass::string::npos) + { + if (end == sass::string::npos) { end = vec.size(); } + return sass::vector(vec.begin() + start, vec.begin() + end); + } + + ///////////////////////////////////////////////////////////////////////// + // Removes the objects in the range [start] inclusive to [end] exclusive. + ///////////////////////////////////////////////////////////////////////// + template + void removeRange(sass::vector& vec, + size_t start, size_t end = sass::string::npos) + { + if (end == sass::string::npos) { end = vec.size(); } + vec.erase(vec.begin() + start, vec.begin() + end); + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + template + size_t indexOf(const sass::vector& vec, const V& item) + { + for (size_t i = 0; i < vec.size(); i += 1) { + if (ObjEqualityFn(vec[i], item)) return i; + } + return sass::string::npos; + } + + ///////////////////////////////////////////////////////////////////////// // Flatten `vector>` to `vector` - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// template T flatten(const sass::vector& all) { @@ -22,12 +83,12 @@ namespace Sass { return flattened; } - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // Expands each element of this Iterable into zero or more elements. // Calls a function on every element and ads all results to flat array - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // Equivalent to dart `cnt.any` - // Pass additional closure variables to `fn` + // Passes additional closure variables to `fn` template T expand(const T& cnt, U fn, Args... args) { T flattened; @@ -39,8 +100,8 @@ namespace Sass { return flattened; } - // ########################################################################## - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// template T flattenInner(const sass::vector& vec) { @@ -52,10 +113,76 @@ namespace Sass { } // EO flattenInner - // ########################################################################## + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + template + sass::vector flattenVertically(sass::vector> lists) + { + sass::vector result; + // Loop until all arrays are exhausted + size_t lvl = 0; bool consumed = false; do { + // aborts when nothing more can be consumed + consumed = false; + // loop over all arrays at the 1st level + for (size_t i = 0, iL = lists.size(); i < iL; ++i) { + // check for items to consume at 2nd level + if (lists[i].size() > lvl) { + // consume item at 2nd level depth + result.emplace_back(lists[i][lvl]); + // maybe we have some more + consumed = true; + } + } + // check next depth level + ++lvl; + } + // abort once nothing is consumed + while (consumed); + // return flat list + return result; + } + // EO flatVertically + + ///////////////////////////////////////////////////////////////////////// + // Implementation of [Map.addAll], but for LibSass. + ///////////////////////////////////////////////////////////////////////// + template + void mapAddAll(K1 dst, K2 source) + { + for (auto& src : source) { + dst[src.first] = src.second; + } + } + + ///////////////////////////////////////////////////////////////////////// + // Like [Map.addAll], but for two-layer maps. + // This avoids copying inner maps from [source] if possible. + ///////////////////////////////////////////////////////////////////////// + template + void mapAddAll2(K1 dst, K2 source) + { + for (auto& src : source) { + mapAddAll(dst[src.first], src.second); + } + } + + // Map> destination, Map> source) { + // source.forEach((key, inner) { + // var innerDestination = destination[key]; + // if (innerDestination != null) { + // innerDestination.addAll(inner); + // } + // else { + // destination[key] = inner; + // } + // }); + // } + + ///////////////////////////////////////////////////////////////////////// // Equivalent to dart `cnt.any` - // Pass additional closure variables to `fn` - // ########################################################################## + // Passes additional closure variables to `fn` + ///////////////////////////////////////////////////////////////////////// template bool hasAny(const T& cnt, U fn, Args... args) { for (const auto& sub : cnt) { @@ -67,10 +194,10 @@ namespace Sass { } // EO hasAny - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // Equivalent to dart `cnt.take(len).any` - // Pass additional closure variables to `fn` - // ########################################################################## + // Passes additional closure variables to `fn` + ///////////////////////////////////////////////////////////////////////// template bool hasSubAny(const T& cnt, size_t len, U fn, Args... args) { for (size_t i = 0; i < len; i++) { @@ -81,9 +208,9 @@ namespace Sass { return false; } - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // Default predicate for lcs algorithm - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// template inline bool lcsIdentityCmp(const T& X, const T& Y, T& result) { @@ -98,9 +225,9 @@ namespace Sass { } // EO lcsIdentityCmp - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // Longest common subsequence with predicate - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// template sass::vector lcs( const sass::vector& X, const sass::vector& Y, @@ -117,9 +244,9 @@ namespace Sass { // To circumvent, allocate one array on the heap // Then use a macro to access via double index // e.g. `size_t L[m][n]` is supported by gcc - size_t* len = new size_t[mm * nn + 1]; - bool* acc = new bool[mm * nn + 1]; - T* res = new T[mm * nn + 1]; + size_t* len = new size_t[mm * nn + 8]; + bool* acc = new bool[mm * nn + 8]{}; + T* res = new T[mm * nn + 8]; #define LEN(x, y) len[(x) * nn + (y)] #define ACC(x, y) acc[(x) * nn + (y)] @@ -159,7 +286,7 @@ namespace Sass { // Note: we push instead of unshift // Note: reverse the vector later // ToDo: is deque more performant? - lcs.push_back(RES(i - 1, j - 1)); + lcs.emplace_back(RES(i - 1, j - 1)); // reduce values of i, j and index i -= 1; j -= 1; index -= 1; } @@ -175,7 +302,7 @@ namespace Sass { } - // reverse now as we used push_back + // reverse now as we used emplace_back std::reverse(lcs.begin(), lcs.end()); // Delete temp memory on heap @@ -191,8 +318,8 @@ namespace Sass { } // EO lcs - // ########################################################################## - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// } diff --git a/src/debug.hpp b/src/debug.hpp deleted file mode 100644 index 43fe05e67e..0000000000 --- a/src/debug.hpp +++ /dev/null @@ -1,43 +0,0 @@ -#ifndef SASS_DEBUG_H -#define SASS_DEBUG_H - -#include - -#ifndef UINT32_MAX - #define UINT32_MAX 0xffffffffU -#endif - -enum dbg_lvl_t : uint32_t { - NONE = 0, - TRIM = 1, - CHUNKS = 2, - SUBWEAVE = 4, - WEAVE = 8, - EXTEND_COMPOUND = 16, - EXTEND_COMPLEX = 32, - LCS = 64, - EXTEND_OBJECT = 128, - ALL = UINT32_MAX -}; - -#ifdef DEBUG - -#ifndef DEBUG_LVL -const uint32_t debug_lvl = UINT32_MAX; -#else -const uint32_t debug_lvl = (DEBUG_LVL); -#endif // DEBUG_LVL - -#define DEBUG_PRINT(lvl, x) if((lvl) & debug_lvl) { std::cerr << x; } -#define DEBUG_PRINTLN(lvl, x) if((lvl) & debug_lvl) { std::cerr << x << std::endl; } -#define DEBUG_EXEC(lvl, x) if((lvl) & debug_lvl) { x; } - -#else // DEBUG - -#define DEBUG_PRINT(lvl, x) -#define DEBUG_PRINTLN(lvl, x) -#define DEBUG_EXEC(lvl, x) - -#endif // DEBUG - -#endif // SASS_DEBUG diff --git a/src/debugger.hpp b/src/debugger.hpp index 31af47218a..d2906a7920 100644 --- a/src/debugger.hpp +++ b/src/debugger.hpp @@ -1,9 +1,9 @@ -#ifndef SASS_DEBUGGER_H -#define SASS_DEBUGGER_H +#ifndef SASS_DEBUGGER_HPP +#define SASS_DEBUGGER_HPP -// sass.hpp must go before all system headers to get the -// __EXTENSIONS__ fix on Solaris. -#include "sass.hpp" +// sass.hpp must go before all system headers +// to get the __EXTENSIONS__ fix on Solaris. +#include "capi_sass.hpp" #include #include @@ -11,21 +11,55 @@ #include #include "ast.hpp" #include "ast_fwd_decl.hpp" +#include "extender.hpp" #include "extension.hpp" #include "ordered_map.hpp" using namespace Sass; -inline void debug_ast(AST_Node* node, sass::string ind = "", Env* env = 0); +////////////////////////////////////////////////////////////////////// +// Define cast template now (need complete type). +// Note: we should have gotten rid of all usage by now. +// Note: only debugger still uses this (move there?). +////////////////////////////////////////////////////////////////////// -inline sass::string debug_vec(const AST_Node* node) { - if (node == NULL) return "null"; - else return node->to_string(); +template +T* Cast(AstNode* ptr) { + return dynamic_cast(ptr); +}; + +template +const T* Cast(const AstNode* ptr) { + return dynamic_cast(ptr); +}; + +inline void debug_ast(AstNode* node, std::string ind = ""); + +inline sass::string ns_name(SelectorNS* s) +{ + if (!s->hasNs()) return s->name(); + else return s->ns() + "|" + s->name(); +} + +inline std::string debug_pstate(SourceSpan pstate) { + std::stringstream str; + str << pstate.getLine() << ":"; + str << pstate.getColumn() << " - "; + return str.str(); } -inline sass::string debug_dude(sass::vector> vec) { - sass::sstream out; +inline sass::string dbgValStr(CssString* str) { + return str ? str->text() : "{nullptr}"; +} + +// inline sass::string debug_vec(const AstNode* node) { +// if (node == NULL) return "null"; +// else return node->inspect(); +// } + +inline std::string debug_dude(sass::vector> vec) { + std::stringstream out; out << "{"; bool joinOut = false; for (auto ct : vec) { @@ -44,25 +78,27 @@ inline sass::string debug_dude(sass::vector> vec) { return out.str(); } -inline sass::string debug_vec(sass::string& str) { +inline std::string debug_vec(std::string& str) { return str; } -inline sass::string debug_vec(Extension& ext) { - sass::sstream out; - out << debug_vec(ext.extender); - out << " {@extend "; - out << debug_vec(ext.target); - if (ext.isOptional) { - out << " !optional"; - } - out << "}"; + +inline std::string debug_vec(EnvKey key) { + return key.norm().c_str(); +} + +inline std::string debug_vec(sass::vector vec) { + std::stringstream out; + out << "["; + SelectorListObj slist = SASS_MEMORY_NEW(SelectorList, SourceSpan::internal("DBG"), std::move(vec)); + out << slist->inspect(); + out << "]"; return out.str(); } template -inline sass::string debug_vec(sass::vector vec) { - sass::sstream out; +inline std::string debug_vec(const sass::vector& vec) { + std::stringstream out; out << "["; for (size_t i = 0; i < vec.size(); i += 1) { if (i > 0) out << ", "; @@ -73,8 +109,8 @@ inline sass::string debug_vec(sass::vector vec) { } template -inline sass::string debug_vec(std::queue vec) { - sass::sstream out; +inline std::string debug_vec(std::queue vec) { + std::stringstream out; out << "{"; for (size_t i = 0; i < vec.size(); i += 1) { if (i > 0) out << ", "; @@ -85,8 +121,8 @@ inline sass::string debug_vec(std::queue vec) { } template -inline sass::string debug_vec(std::map vec) { - sass::sstream out; +inline std::string debug_vec(std::map vec) { + std::stringstream out; out << "{"; bool joinit = false; for (auto it = vec.begin(); it != vec.end(); it++) @@ -102,8 +138,8 @@ inline sass::string debug_vec(std::map vec) { } template -inline sass::string debug_vec(const ordered_map& vec) { - sass::sstream out; +inline std::string debug_vec(const ordered_map& vec) { + std::stringstream out; out << "{"; bool joinit = false; for (auto it = vec.begin(); it != vec.end(); it++) @@ -118,8 +154,8 @@ inline sass::string debug_vec(const ordered_map& vec) { } template -inline sass::string debug_vec(std::unordered_map vec) { - sass::sstream out; +inline std::string debug_vec(std::unordered_map vec) { + std::stringstream out; out << "{"; bool joinit = false; for (auto it = vec.begin(); it != vec.end(); it++) @@ -135,8 +171,8 @@ inline sass::string debug_vec(std::unordered_map vec) { } template -inline sass::string debug_keys(std::unordered_map vec) { - sass::sstream out; +inline std::string debug_keys(std::unordered_map vec) { + std::stringstream out; out << "{"; bool joinit = false; for (auto it = vec.begin(); it != vec.end(); it++) @@ -149,14 +185,14 @@ inline sass::string debug_keys(std::unordered_map vec) { return out.str(); } -inline sass::string debug_vec(ExtListSelSet& vec) { - sass::sstream out; +inline std::string debug_vec(ExtListSelSet& vec) { + std::stringstream out; out << "{"; bool joinit = false; for (auto it = vec.begin(); it != vec.end(); it++) { if (joinit) out << ", "; - out << debug_vec(*it); // string (key) + // out << debug_vec(*it); // string (key) joinit = true; } out << "}"; @@ -165,8 +201,8 @@ inline sass::string debug_vec(ExtListSelSet& vec) { /* template -inline sass::string debug_values(tsl::ordered_map vec) { - sass::sstream out; +inline std::string debug_values(tsl::ordered_map vec) { + std::stringstream out; out << "{"; bool joinit = false; for (auto it = vec.begin(); it != vec.end(); it++) @@ -178,10 +214,10 @@ inline sass::string debug_values(tsl::ordered_map vec) { out << "}"; return out.str(); } - + template -inline sass::string debug_vec(tsl::ordered_map vec) { - sass::sstream out; +inline std::string debug_vec(tsl::ordered_map vec) { + std::stringstream out; out << "{"; bool joinit = false; for (auto it = vec.begin(); it != vec.end(); it++) @@ -197,8 +233,8 @@ inline sass::string debug_vec(tsl::ordered_map vec) { } template -inline sass::string debug_vals(tsl::ordered_map vec) { - sass::sstream out; +inline std::string debug_vals(tsl::ordered_map vec) { + std::stringstream out; out << "{"; bool joinit = false; for (auto it = vec.begin(); it != vec.end(); it++) @@ -212,8 +248,8 @@ inline sass::string debug_vals(tsl::ordered_map vec) { } template -inline sass::string debug_keys(tsl::ordered_map vec) { - sass::sstream out; +inline std::string debug_keys(tsl::ordered_map vec) { + std::stringstream out; out << "{"; bool joinit = false; for (auto it = vec.begin(); it != vec.end(); it++) @@ -228,8 +264,8 @@ inline sass::string debug_keys(tsl::ordered_map vec) { */ template -inline sass::string debug_vec(std::set vec) { - sass::sstream out; +inline std::string debug_vec(std::set vec) { + std::stringstream out; out << "{"; bool joinit = false; for (auto item : vec) { @@ -243,8 +279,8 @@ inline sass::string debug_vec(std::set vec) { /* template -inline sass::string debug_vec(tsl::ordered_set vec) { - sass::sstream out; +inline std::string debug_vec(tsl::ordered_set vec) { + std::stringstream out; out << "{"; bool joinit = false; for (auto item : vec) { @@ -258,8 +294,8 @@ inline sass::string debug_vec(tsl::ordered_set vec) { */ template -inline sass::string debug_vec(std::unordered_set vec) { - sass::sstream out; +inline std::string debug_vec(std::unordered_set vec) { + std::stringstream out; out << "{"; bool joinit = false; for (auto item : vec) { @@ -271,264 +307,462 @@ inline sass::string debug_vec(std::unordered_set vec) { return out.str(); } -inline sass::string debug_bool(bool val) { +inline std::string debug_bool(bool val) { return val ? "true" : "false"; } -inline sass::string debug_vec(ExtSmplSelSet* node) { - if (node == NULL) return "null"; - else return debug_vec(*node); +inline std::string debug_vec(ExtSmplSelSet* node) { + // if (node == NULL) return "null"; + // else return debug_vec(*node); + return ""; } -inline void debug_ast(const AST_Node* node, sass::string ind = "", Env* env = 0) { - debug_ast(const_cast(node), ind, env); +inline void debug_ast(const AstNode* node, std::string ind = "") { + debug_ast(const_cast(node), ind); } -inline sass::string str_replace(sass::string str, const sass::string& oldStr, const sass::string& newStr) +inline sass::string string_replace(sass::string str, const sass::string& oldStr, const sass::string& newStr) { size_t pos = 0; - while((pos = str.find(oldStr, pos)) != sass::string::npos) + while ((pos = str.find(oldStr, pos)) != std::string::npos) { - str.replace(pos, oldStr.length(), newStr); - pos += newStr.length(); + str.replace(pos, oldStr.length(), newStr); + pos += newStr.length(); } return str; } inline sass::string prettyprint(const sass::string& str) { - sass::string clean = str_replace(str, "\n", "\\n"); - clean = str_replace(clean, " ", "\\t"); - clean = str_replace(clean, "\r", "\\r"); + sass::string clean = string_replace(str, "\n", "\\n"); + clean = string_replace(clean, " ", "\\t"); + clean = string_replace(clean, "\r", "\\r"); return clean; } -inline sass::string longToHex(long long t) { - sass::sstream is; +inline std::string longToHex(long long t) { + std::stringstream is; is << std::hex << t; return is.str(); } -inline sass::string pstate_source_position(AST_Node* node) +inline std::string parent_block(EnvRefs* node) +{ + std::stringstream str; + if (!node) return ""; + str << "SG: " << (node->isSemiGlobal ? "true" : "false"); + return str.str(); +} + +inline std::string pstate_source_position(const SourceSpan& pstate) +{ + std::stringstream str; + if (pstate.getSource()) { + str << (pstate.getSrcIdx() == std::string::npos ? 9999999 : pstate.getSrcIdx()) + << "@[" << (pstate.position.line) << ":" << (pstate.position.column) << "]" + << "+[" << (pstate.span.line) << ":" << (pstate.span.column) << "]"; + } + else { + str << "[NOSRC]"; + } +#ifdef DEBUG_SHARED_PTR + str << "x" << node->getRefCount() << "" + << " {#" << node->objId << "}" + << " " << node->getDbgFile() + << "@" << node->getDbgLine(); +#endif + return str.str(); +} + +inline std::string pstate_source_position(AstNode* node) { - sass::sstream str; - Offset start(node->pstate().position); - Offset end(start + node->pstate().offset); - size_t file = node->pstate().getSrcId(); - str << (file == sass::string::npos ? 99999999 : file) - << "@[" << start.line << ":" << start.column << "]" - << "-[" << end.line << ":" << end.column << "]"; + std::stringstream str; + SourceSpan pstate(node->pstate()); + if (pstate.getSource()) { + str << (pstate.getSrcIdx() == std::string::npos ? 9999999 : pstate.getSrcIdx()) + << "@[" << (pstate.position.line) << ":" << (pstate.position.column) << "]" + << "+[" << (pstate.span.line) << ":" << (pstate.span.column) << "]" + << "X" << node->refcount; + } + else { + str << "[NOSRC]"; + } #ifdef DEBUG_SHARED_PTR - str << "x" << node->getRefCount() << "" - << " " << node->getDbgFile() - << "@" << node->getDbgLine(); + str << "x" << node->getRefCount() << "" + << " {#" << node->objId << "}" + << " " << node->getDbgFile() + << "@" << node->getDbgLine(); #endif return str.str(); } -inline void debug_ast(AST_Node* node, sass::string ind, Env* env) +inline void debug_block(ParentStatement* node, std::string ind) +{ + for (auto item : node->elements()) { + debug_ast(item, ind); + } +} + +inline void debug_block(CssParentNode* node, std::string ind) +{ + for (auto item : node->elements()) { + debug_ast(item, ind); + } +} + +inline void debug_block(Root* node, std::string ind) +{ + for (auto item : node->elements()) { + debug_ast(item, ind); + } +} + +inline void debug_css_parent_node(CssParentNode* rule, std::string ind = "") +{ + std::cerr << ind << "CssParentNode " << rule; + std::cerr << " (" << pstate_source_position(rule) << ")"; + std::cerr << std::endl; + debug_block(rule, ind + " "); +} + +inline void debug_idxs(Env* env) { + // return; + if (env) { + std::cerr << " "; + std::cerr << "{V:" << env->idxs->varIdxs.size(); + std::cerr << "|M:" << env->idxs->mixIdxs.size(); + std::cerr << "|F:" << env->idxs->fnIdxs.size(); + if (env->idxs->module) { + std::cerr << "|U:" << env->idxs->module->upstream.size(); + std::cerr << "|FV:" << env->idxs->module->mergedFwdVar.size(); + std::cerr << "|FM:" << env->idxs->module->mergedFwdMix.size(); + std::cerr << "|FF:" << env->idxs->module->mergedFwdFn.size(); + } + std::cerr << "}"; + } + else { + std::cerr << " {nil}"; + } + +} + +inline void debug_ast(AstNode* node, std::string ind) { if (node == 0) return; if (ind == "") std::cerr << "####################################################################\n"; - if (Cast(node)) { - Bubble* bubble = Cast(node); - std::cerr << ind << "Bubble " << bubble; - std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << " " << bubble->tabs(); + if (Cast(node)) { + Root* root = Cast(node); + std::cerr << ind << "Root " << root; + std::cerr << " (" << pstate_source_position(root) << ")"; + debug_idxs(root); std::cerr << std::endl; - debug_ast(bubble->node(), ind + " ", env); - } else if (Cast(node)) { - Trace* trace = Cast(node); - std::cerr << ind << "Trace " << trace; - std::cerr << " (" << pstate_source_position(node) << ")" - << " [name:" << trace->name() << ", type: " << trace->type() << "]" - << std::endl; - debug_ast(trace->block(), ind + " ", env); - } else if (Cast(node)) { + debug_block(root, ind + " "); + } + else if (Cast(node)) { + CssRoot* root = Cast(node); + std::cerr << ind << "CssRoot " << root; + std::cerr << " (" << pstate_source_position(root) << ")"; + std::cerr << std::endl; + debug_block(root, ind + " "); + } + else if (Cast(node)) { + CssComment* comment = Cast(node); + std::cerr << ind << "CssComment " << comment; + std::cerr << " (" << pstate_source_position(comment) << ")"; + std::cerr << std::endl; + } + else if (Cast(node)) { AtRootRule* root_block = Cast(node); std::cerr << ind << "AtRootRule " << root_block; std::cerr << " (" << pstate_source_position(node) << ")"; std::cerr << " " << root_block->tabs(); std::cerr << std::endl; - debug_ast(root_block->expression(), ind + ":", env); - debug_ast(root_block->block(), ind + " ", env); - } else if (Cast(node)) { + debug_ast(root_block->query(), ind + ":"); + debug_block(root_block, ind + " "); + } + else if (Cast(node)) { + AtRootQuery* query = Cast(node); + std::cerr << ind << "AtRootQuery " << query; + std::cerr << " (" << pstate_source_position(node) << ")"; + // std::cerr << " <" << query->inspect() << ">"; + std::cerr << std::endl; + } + else if (Cast(node)) { SelectorList* selector = Cast(node); std::cerr << ind << "SelectorList " << selector; std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << " <" << selector->hash() << ">"; - std::cerr << (selector->is_invisible() ? " [is_invisible]" : " -"); - std::cerr << (selector->isInvisible() ? " [isInvisible]" : " -"); - std::cerr << (selector->has_real_parent_ref() ? " [real-parent]": " -"); + // std::cerr << (selector->hasInvisible() ? " [hasInvisible]" : " -"); + // std::cerr << (selector->has_real_parent_ref() ? " [real-parent]" : " -"); std::cerr << std::endl; - for(const ComplexSelector_Obj& i : selector->elements()) { debug_ast(i, ind + " ", env); } + for (const ComplexSelectorObj& i : selector->elements()) { debug_ast(i, ind + " "); } - } else if (Cast(node)) { + } + else if (Cast(node)) { ComplexSelector* selector = Cast(node); - std::cerr << ind << "ComplexSelector " << selector - << " (" << pstate_source_position(node) << ")" - << " <" << selector->hash() << ">" - << " [" << (selector->chroots() ? "CHROOT" : "CONNECT") << "]" - << " [length:" << longToHex(selector->length()) << "]" - << " [weight:" << longToHex(selector->specificity()) << "]" - << (selector->is_invisible() ? " [is_invisible]" : " -") - << (selector->isInvisible() ? " [isInvisible]" : " -") - << (selector->hasPreLineFeed() ? " [hasPreLineFeed]" : " -") - - // << (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)) { - SelectorCombinator* selector = Cast(node); - std::cerr << ind << "SelectorCombinator " << selector - << " (" << pstate_source_position(node) << ")" - << " <" << selector->hash() << ">" - << " [weight:" << longToHex(selector->specificity()) << "]" - << (selector->has_real_parent_ref() ? " [real parent]": " -") - << " -- "; - - sass::string del; - switch (selector->combinator()) { - case SelectorCombinator::CHILD: del = ">"; break; - case SelectorCombinator::GENERAL: del = "~"; break; - case SelectorCombinator::ADJACENT: del = "+"; break; + std::cerr << ind << "ComplexSelector " << selector; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " [" << (selector->chroots() ? "CHROOT" : "CONNECT") << "]"; + + // << " [" << (selector->hasInvisible() ? "hasInvisible" : "") << "]" + + std::cerr << " [length:" << longToHex(selector->size()) << "]"; + std::cerr << " [weight:" << longToHex(selector->specificity()) << "]"; + // << (selector->hasInvisible() ? " [hasInvisible]" : " -") + std::cerr << (selector->hasPreLineFeed() ? " [hasPreLineFeed]" : " -"); + + // << (selector->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]": " -") + std::cerr << " -- \n"; + + if (selector->leadingCombinators().size() > 0) { + for (auto asd : selector->leadingCombinators()) { + ind += std::string(asd->toString().c_str()); + ind += " "; } + } + else { + ind += " "; + } + + + for (const CplxSelComponentObj& i : selector->elements()) { debug_ast(i, ind); } - std::cerr << "[" << del << "]" << "\n"; - } else if (Cast(node)) { + } + else if (Cast(node)) { + SelectorCombinator* combinator = Cast(node); + std::cerr << ind << "SelectorCombinator " << combinator + << " (" << pstate_source_position(node) << ")" + // << " [weight:" << longToHex(component->specificity()) << "]" + // << (selector->has_real_parent_ref() ? " [real parent]" : " -") + << " [ " << combinator->toString() << " ]" + << "\n"; + } + else if (Cast(node)) { + CplxSelComponent* component = Cast(node); + std::cerr << ind << "SelectorComponent " << component + << " (" << pstate_source_position(node) << ")" + // << " [weight:" << longToHex(component->specificity()) << "]" + // << (selector->has_real_parent_ref() ? " [real parent]" : " -") + << "\n"; + debug_ast(component->selector(), ind + " "); + for (const SelectorCombinatorObj& i : component->combinators()) { debug_ast(i, ind + " "); } + + // if (!component->combinators().empty()) { + // std::cerr << ind << " postfix combinators ["; + // for (const SelectorCombinator* combinator : component->combinators()) { + // std::cerr << combinator->toString(); + // } + // std::cerr << " ]"; + // std::cerr << " (" << pstate_source_position(component->cpstate()) << ")"; + // std::cerr << "\n"; + // } + } +// else if (Cast(node)) { +// SelectorCombinator* selector = Cast(node); +// std::cerr << ind << "SelectorCombinator " << selector +// << " (" << pstate_source_position(node) << ")" +// << " [weight:" << longToHex(selector->specificity()) << "]" +// // << (selector->has_real_parent_ref() ? " [real parent]" : " -") +// << " -- "; +// +// std::string del; +// switch (selector->combinator()) { +// case SelectorCombinator::CHILD: del = ">"; break; +// case SelectorCombinator::GENERAL: del = "~"; break; +// case SelectorCombinator::ADJACENT: del = "+"; break; +// } +// +// std::cerr << "[" << del << "]" << "\n"; +// +// } + else if (Cast(node)) { CompoundSelector* selector = Cast(node); std::cerr << ind << "CompoundSelector " << selector; std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << " <" << selector->hash() << ">"; - std::cerr << (selector->hasRealParent() ? " [REAL PARENT]" : "") << ">"; + std::cerr << (selector->withExplicitParent() ? " [EXPLICIT PARENT]" : "") << ">"; std::cerr << " [weight:" << longToHex(selector->specificity()) << "]"; std::cerr << (selector->hasPostLineBreak() ? " [hasPostLineBreak]" : " -"); - std::cerr << (selector->is_invisible() ? " [is_invisible]" : " -"); - std::cerr << (selector->isInvisible() ? " [isInvisible]" : " -"); + // std::cerr << (selector->hasInvisible() ? " [hasInvisible]" : " -"); std::cerr << "\n"; - for(const SimpleSelector_Obj& i : selector->elements()) { debug_ast(i, ind + " ", env); } + for (const SimpleSelectorObj& i : selector->elements()) { debug_ast(i, ind + " "); } + + } + else if (Cast(node)) { + SelectorExpression* selector = Cast(node); + std::cerr << ind << "SelectorExpression " << selector; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << std::endl; + } + + else if (Cast(node)) { + BooleanExpression* selector = Cast(node); + std::cerr << ind << "SelectorExpression " << selector; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << std::endl; + } + + else if (Cast(node)) { + ColorExpression* selector = Cast(node); + std::cerr << ind << "SelectorExpression " << selector; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << std::endl; + } - } else if (Cast(node)) { - Parent_Reference* selector = Cast(node); - std::cerr << ind << "Parent_Reference " << selector; + else if (Cast(node)) { + NumberExpression* selector = Cast(node); + std::cerr << ind << "NumberExpression " << selector; std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << " <" << selector->hash() << ">"; std::cerr << std::endl; + } - } else if (Cast(node)) { + else if (Cast(node)) { + NullExpression* selector = Cast(node); + std::cerr << ind << "NullExpression " << selector; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << std::endl; + } + else if (Cast(node)) { PseudoSelector* selector = Cast(node); std::cerr << ind << "PseudoSelector " << selector; std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << " <" << selector->hash() << ">"; - std::cerr << " <<" << selector->ns_name() << ">>"; - std::cerr << (selector->isClass() ? " [isClass]": " -"); - std::cerr << (selector->isSyntacticClass() ? " [isSyntacticClass]": " -"); - std::cerr << (selector->has_real_parent_ref(nullptr) ? " [real parent]" : " -"); + std::cerr << " <<" << selector->name() << ">>"; + std::cerr << (selector->isClass() ? " [isClass]" : " -"); + std::cerr << (selector->isPseudoElement() ? " [isPseudoElement]" : " -"); + std::cerr << (selector->isSyntacticClass() ? " [isSyntacticClass]" : " -"); + // std::cerr << (selector->hasInvisible() ? " [hasInvisible]" : " -"); std::cerr << std::endl; - debug_ast(selector->argument(), ind + " <= ", env); - debug_ast(selector->selector(), ind + " || ", env); - } else if (Cast(node)) { + std::cerr << ind << " <= " << selector->argument() << std::endl; + debug_ast(selector->selector(), ind + " || "); + } + else if (Cast(node)) { AttributeSelector* selector = Cast(node); std::cerr << ind << "AttributeSelector " << selector; std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << " <" << selector->hash() << ">"; - std::cerr << " <<" << selector->ns_name() << ">>"; + std::cerr << " <<" << ns_name(selector) << ">>"; std::cerr << std::endl; - debug_ast(selector->value(), ind + "[" + selector->matcher() + "] ", env); - } else if (Cast(node)) { + std::cerr << ind << "[" << selector->op() << "] "; + std::cerr << selector->value() << std::endl; + } + else if (Cast(node)) { ClassSelector* selector = Cast(node); std::cerr << ind << "ClassSelector " << selector; std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << " <" << selector->hash() << ">"; - std::cerr << " <<" << selector->ns_name() << ">>"; + std::cerr << " <<" << selector->name() << ">>"; std::cerr << std::endl; - } else if (Cast(node)) { + } + else if (Cast(node)) { IDSelector* selector = Cast(node); std::cerr << ind << "IDSelector " << selector; std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << " <" << selector->hash() << ">"; - std::cerr << " <<" << selector->ns_name() << ">>"; + std::cerr << " <<" << selector->name() << ">>"; std::cerr << std::endl; - } else if (Cast(node)) { + } + else if (Cast(node)) { TypeSelector* selector = Cast(node); std::cerr << ind << "TypeSelector " << selector; std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << " <" << selector->hash() << ">"; - std::cerr << " <<" << selector->ns_name() << ">>"; + std::cerr << " <<" << ns_name(selector) << ">>"; + // std::cerr << " <" << prettyprint(selector->pstate().token.ws_before()) << ">"; std::cerr << std::endl; - } else if (Cast(node)) { + } + else if (Cast(node)) { PlaceholderSelector* selector = Cast(node); - std::cerr << ind << "PlaceholderSelector [" << selector->ns_name() << "] " << selector; + std::cerr << ind << "PlaceholderSelector [" << selector->name() << "] " << selector; std::cerr << " (" << pstate_source_position(selector) << ")" - << " <" << selector->hash() << ">" - << (selector->isInvisible() ? " [isInvisible]" : " -") - << std::endl; + // << (selector->hasInvisible() ? " [hasInvisible]" : " -") + << std::endl; - } else if (Cast(node)) { + } + else if (Cast(node)) { SimpleSelector* selector = Cast(node); std::cerr << ind << "SimpleSelector " << selector; std::cerr << " (" << pstate_source_position(node) << ")"; - } else if (Cast(node)) { - Selector_Schema* selector = Cast(node); - std::cerr << ind << "Selector_Schema " << selector; - std::cerr << " (" << pstate_source_position(node) << ")" - << (selector->connect_parent() ? " [connect-parent]": " -") - << std::endl; - - debug_ast(selector->contents(), ind + " "); - // for(auto i : selector->elements()) { debug_ast(i, ind + " ", env); } - - } else if (Cast(node)) { + } + else if (Cast(node)) { Selector* selector = Cast(node); std::cerr << ind << "Selector " << selector; std::cerr << " (" << pstate_source_position(node) << ")" - << std::endl; + << std::endl; + } + else if (Cast(node)) { + ContentRule* rule = Cast(node); + std::cerr << ind << "ContentRule " << rule; + std::cerr << " (" << pstate_source_position(rule) << ")"; + std::cerr << std::endl; + debug_ast(rule->arguments(), ind + " =@ "); + } + else if (Cast(node)) { + ForwardRule* rule = Cast(node); + std::cerr << ind << "ForwardRule " << rule; + std::cerr << " (" << pstate_source_position(rule) << ")"; + debug_idxs(rule->module32()); + std::cerr << std::endl; + debug_ast(rule->root47(), ind + " =@ "); - } else if (Cast(node)) { - Media_Query_Expression* block = Cast(node); - std::cerr << ind << "Media_Query_Expression " << block; - std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << (block->is_interpolated() ? " [is_interpolated]": " -") - << std::endl; - debug_ast(block->feature(), ind + " feature) "); - debug_ast(block->value(), ind + " value) "); + } + else if (Cast(node)) { + UseRule* rule = Cast(node); + std::cerr << ind << "UseRule " << rule; + std::cerr << " (" << pstate_source_position(rule) << ")"; + debug_idxs(rule->module32()); + std::cerr << std::endl; + debug_ast(rule->root47(), ind + " =@ "); - } else if (Cast(node)) { - Media_Query* block = Cast(node); - std::cerr << ind << "Media_Query " << block; - std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << (block->is_negated() ? " [is_negated]": " -") - << (block->is_restricted() ? " [is_restricted]": " -") - << std::endl; - debug_ast(block->media_type(), ind + " "); - for(const auto& i : block->elements()) { debug_ast(i, ind + " ", env); } } + else if (Cast(node)) { + UserDefinedCallable* rule = Cast(node); + std::cerr << ind << "UserDefinedCallable " << rule; + std::cerr << " (" << pstate_source_position(rule) << ")"; + std::cerr << std::endl; + debug_ast(rule->declaration(), ind + " =@ "); + } + else if (Cast(node)) { + ValueExpression* rule = Cast(node); + std::cerr << ind << "ValueExpression " << rule; + std::cerr << " (" << pstate_source_position(rule) << ")"; + std::cerr << std::endl; + debug_ast(rule->value(), ind + " =@ "); + } + else if (Cast(node)) { MediaRule* rule = Cast(node); std::cerr << ind << "MediaRule " << rule; std::cerr << " (" << pstate_source_position(rule) << ")"; std::cerr << " " << rule->tabs() << std::endl; - debug_ast(rule->schema(), ind + " =@ "); - debug_ast(rule->block(), ind + " "); + debug_ast(rule->query(), ind + " =@ "); + debug_block(rule, ind + " "); } else if (Cast(node)) { CssMediaRule* rule = Cast(node); std::cerr << ind << "CssMediaRule " << rule; std::cerr << " (" << pstate_source_position(rule) << ")"; - std::cerr << " " << rule->tabs() << std::endl; + for (auto item : rule->queries()) { + debug_ast(item, ind + "() "); + } for (auto item : rule->elements()) { - debug_ast(item, ind + " == "); + debug_ast(item, ind + " !! "); } - debug_ast(rule->block(), ind + " "); + debug_css_parent_node(rule, ind + " :: "); + } + else if (Cast(node)) { + CssDeclaration* rule = Cast(node); + std::cerr << ind << "CssDeclaration " << rule; + std::cerr << " (" << pstate_source_position(rule) << ")"; + std::cerr << "\n"; + debug_ast(rule->name(), ind + " name: "); + debug_ast(rule->value(), ind + " prop: "); + } + else if (Cast(node)) { + CssString* rule = Cast(node); + std::cerr << ind << "CssString " << rule; + std::cerr << " (" << pstate_source_position(rule) << ")"; + std::cerr << " <" << rule->text() << ">"; + std::cerr << std::endl; } else if (Cast(node)) { CssMediaQuery* query = Cast(node); @@ -538,426 +772,597 @@ inline void debug_ast(AST_Node* node, sass::string ind, Env* env) std::cerr << " [" << (query->type()) << "] "; std::cerr << " " << debug_vec(query->features()); std::cerr << std::endl; - } else if (Cast(node)) { + } + else if (Cast(node)) { SupportsRule* block = Cast(node); std::cerr << ind << "SupportsRule " << block; std::cerr << " (" << pstate_source_position(node) << ")"; std::cerr << " " << block->tabs() << std::endl; debug_ast(block->condition(), ind + " =@ "); - debug_ast(block->block(), ind + " <>"); - } else if (Cast(node)) { + debug_block(block, ind + " <>"); + std::cerr << std::endl; + } + else if (Cast(node)) + { + CssSupportsRule* block = Cast(node); + std::cerr << ind << "CssSupportsRule " << block; + std::cerr << " (" << pstate_source_position(node) << ")"; + debug_ast(block->condition(), ind + " =@ "); + for (auto stmt : block->elements()) { + debug_ast(stmt, ind + " <>"); + } + } + else if (Cast(node)) { + SupportsRule* block = Cast(node); + std::cerr << ind << "SupportsRule " << block; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " " << block->tabs() << std::endl; + debug_ast(block->condition(), ind + " =@ "); + debug_block(block, ind + " <>"); + } + else if (Cast(node)) { SupportsOperation* block = Cast(node); std::cerr << ind << "SupportsOperation " << block; std::cerr << " (" << pstate_source_position(node) << ")" - << std::endl; + << std::endl; debug_ast(block->left(), ind + " left) "); debug_ast(block->right(), ind + " right) "); - } else if (Cast(node)) { + } + else if (Cast(node)) { + SupportsFunction* block = Cast(node); + std::cerr << ind << "SupportsFunction " << block; + std::cerr << " (" << pstate_source_position(node) << ")" + << std::endl; + // debug_ast(block->condition(), ind + " condition) "); + } + else if (Cast(node)) { + SupportsAnything* block = Cast(node); + std::cerr << ind << "SupportsAnything " << block; + std::cerr << " (" << pstate_source_position(node) << ")" + << std::endl; + // debug_ast(block->condition(), ind + " condition) "); + } + else if (Cast(node)) { SupportsNegation* block = Cast(node); std::cerr << ind << "SupportsNegation " << block; std::cerr << " (" << pstate_source_position(node) << ")" - << std::endl; + << std::endl; debug_ast(block->condition(), ind + " condition) "); - } else if (Cast(node)) { - At_Root_Query* block = Cast(node); - std::cerr << ind << "At_Root_Query " << block; - std::cerr << " (" << pstate_source_position(node) << ")" - << std::endl; - debug_ast(block->feature(), ind + " feature) "); - debug_ast(block->value(), ind + " value) "); - } else if (Cast(node)) { + } + else if (Cast(node)) { SupportsDeclaration* block = Cast(node); std::cerr << ind << "SupportsDeclaration " << block; std::cerr << " (" << pstate_source_position(node) << ")" - << std::endl; + << std::endl; debug_ast(block->feature(), ind + " feature) "); debug_ast(block->value(), ind + " value) "); - } else if (Cast(node)) { - Block* root_block = Cast(node); - std::cerr << ind << "Block " << root_block; - std::cerr << " (" << pstate_source_position(node) << ")"; - if (root_block->is_root()) std::cerr << " [root]"; - if (root_block->isInvisible()) std::cerr << " [isInvisible]"; - std::cerr << " " << root_block->tabs() << std::endl; - for(const Statement_Obj& i : root_block->elements()) { debug_ast(i, ind + " ", env); } - } else if (Cast(node)) { - WarningRule* block = Cast(node); - std::cerr << ind << "WarningRule " << block; + } + else if (Cast< SupportsCondition>(node)) { + SupportsCondition* block = Cast(node); + std::cerr << ind << "SupportsDeclaration " << block; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << std::endl; + + } + else if (Cast(node)) { + WarnRule* block = Cast(node); + std::cerr << ind << "WarnRule " << block; std::cerr << " (" << pstate_source_position(node) << ")"; std::cerr << " " << block->tabs() << std::endl; - debug_ast(block->message(), ind + " : "); - } else if (Cast(node)) { + debug_ast(block->expression(), ind + " : "); + } + else if (Cast(node)) { ErrorRule* block = Cast(node); std::cerr << ind << "ErrorRule " << block; std::cerr << " (" << pstate_source_position(node) << ")"; std::cerr << " " << block->tabs() << std::endl; - } else if (Cast(node)) { + debug_ast(block->expression(), ind + " "); + } + else if (Cast(node)) { DebugRule* block = Cast(node); std::cerr << ind << "DebugRule " << block; std::cerr << " (" << pstate_source_position(node) << ")"; std::cerr << " " << block->tabs() << std::endl; - debug_ast(block->value(), ind + " "); - } else if (Cast(node)) { - Comment* block = Cast(node); - std::cerr << ind << "Comment " << block; + debug_ast(block->expression(), ind + " "); + } + else if (Cast(node)) { + LoudComment* block = Cast(node); + std::cerr << ind << "LoudComment " << block; std::cerr << " (" << pstate_source_position(node) << ")"; std::cerr << " " << block->tabs() << std::endl; - debug_ast(block->text(), ind + "// ", env); - } else if (Cast(node)) { - If* block = Cast(node); - std::cerr << ind << "If " << block; + debug_ast(block->text(), ind + "// "); + } + else if (Cast(node)) { + SilentComment* block = Cast(node); + std::cerr << ind << "SilentComment " << block; std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " " << block->tabs() << " [" << block->text() << "]" << std::endl; + // debug_ast(block->text(), ind + "// "); + } + else if (Cast(node)) { + IfRule* block = Cast(node); + std::cerr << ind << "IfRule " << block; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " [" << parent_block(block->idxs) << "]"; std::cerr << " " << block->tabs() << std::endl; debug_ast(block->predicate(), ind + " = "); - debug_ast(block->block(), ind + " <>"); + debug_block(block, ind + " <>"); debug_ast(block->alternative(), ind + " ><"); - } else if (Cast(node)) { - Return* block = Cast(node); - std::cerr << ind << "Return " << block; + } + else if (Cast(node)) { + ReturnRule* block = Cast(node); + std::cerr << ind << "ReturnRule " << block; std::cerr << " (" << pstate_source_position(node) << ")"; std::cerr << " " << block->tabs(); - std::cerr << " [" << block->value()->to_string() << "]" << std::endl; - } else if (Cast(node)) { + std::cerr << std::endl; + debug_ast(block->value(), ind + " => "); + // std::cerr << " [" << block->value()->inspect() << "]"; + } + else if (Cast(node)) { ExtendRule* block = Cast(node); std::cerr << ind << "ExtendRule " << block; std::cerr << " (" << pstate_source_position(node) << ")"; std::cerr << " " << block->tabs() << std::endl; - debug_ast(block->selector(), ind + "-> ", env); - } else if (Cast(node)) { - Content* block = Cast(node); - std::cerr << ind << "Content " << block; + debug_ast(block->selector(), ind + "-> "); + } + else if (Cast(node)) { + ContentRule* block = Cast(node); + std::cerr << ind << "ContentRule " << block; std::cerr << " (" << pstate_source_position(node) << ")"; std::cerr << " " << block->tabs() << std::endl; - debug_ast(block->arguments(), ind + " args: ", env); - } else if (Cast(node)) { - Import_Stub* block = Cast(node); - std::cerr << ind << "Import_Stub " << block; + debug_ast(block->arguments(), ind + " args: "); + } + else if (Cast(node)) { + StaticImport* block = Cast(node); + std::cerr << ind << "StaticImport " << block; std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << " [" << block->imp_path() << "] "; - std::cerr << " " << block->tabs() << std::endl; - } else if (Cast(node)) { - Import* block = Cast(node); - std::cerr << ind << "Import " << block; + // std::cerr << " [" << block->imp_path() << "] "; + // std::cerr << " " << block->tabs(); + std::cerr << std::endl; + debug_ast(block->url(), "url: "); + debug_ast(block->modifiers(), "modifiers: "); + } + else if (Cast(node)) { + CssImport* block = Cast(node); + std::cerr << ind << "CssImport " << block; std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << " " << block->tabs() << std::endl; - // sass::vector files_; - for (auto imp : block->urls()) debug_ast(imp, ind + "@: ", env); - debug_ast(block->import_queries(), ind + "@@ "); - } else if (Cast(node)) { - Assignment* block = Cast(node); - std::cerr << ind << "Assignment " << block; - std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << " <<" << block->variable() << ">> " << block->tabs() << std::endl; - debug_ast(block->value(), ind + "=", env); - } else if (Cast(node)) { + std::cerr << " [" << block->url()->text() << "] "; + std::cerr << std::endl; + } + else if (Cast(node)) { + IncludeImport* block = Cast(node); + std::cerr << ind << "IncludeImport " << block; + std::cerr << " (" << pstate_source_position(node) << ")"; + debug_idxs(block->module32()); + std::cerr << std::endl; + debug_ast(block->root47(), ind + " @ "); + } + else if (Cast(node)) { + ImportRule* block = Cast(node); + std::cerr << ind << "ImportRule " << block; + std::cerr << " (" << pstate_source_position(node) << ")"; + // std::cerr << " [" << block->imp_path() << "] "; + // std::cerr << " " << block->tabs(); + std::cerr << std::endl; + for (auto imp : block->elements()) debug_ast(imp, ind + "@: "); + } + else if (Cast(node)) { + AssignRule* block = Cast(node); + std::cerr << ind << "AssignRule " << block; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " <<" << block->variable().orig() << ">> " << block->tabs(); + std::cerr << " vidx("; + //bool join = false; + //for (auto pidx : block->vidxs()) { + // if (join) std::cerr << ", "; + // std::cerr << pidx.toString(); + // join = true; + //} + std::cerr << ")" << std::endl; + + debug_ast(block->value(), ind + "="); + } + else if (Cast(node)) { Declaration* block = Cast(node); std::cerr << ind << "Declaration " << block; std::cerr << " (" << pstate_source_position(node) << ")"; std::cerr << " [is_custom_property: " << block->is_custom_property() << "] "; std::cerr << " " << block->tabs() << std::endl; - debug_ast(block->property(), ind + " prop: ", env); - debug_ast(block->value(), ind + " value: ", env); - debug_ast(block->block(), ind + " ", env); - } else if (Cast(node)) { - Keyframe_Rule* ParentStatement = Cast(node); - std::cerr << ind << "Keyframe_Rule " << ParentStatement; - std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << " " << ParentStatement->tabs() << std::endl; - if (ParentStatement->name()) debug_ast(ParentStatement->name(), ind + "@"); - if (ParentStatement->block()) for(const Statement_Obj& i : ParentStatement->block()->elements()) { debug_ast(i, ind + " ", env); } - } else if (Cast(node)) { + debug_ast(block->name(), ind + " name: "); + debug_ast(block->value(), ind + " value: "); + debug_block(block, ind + " "); + } + else if (Cast(node)) { AtRule* block = Cast(node); std::cerr << ind << "AtRule " << block; std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << " [" << block->keyword() << "] " << block->tabs() << std::endl; - debug_ast(block->selector(), ind + "~", env); - debug_ast(block->value(), ind + "+", env); - if (block->block()) for(const Statement_Obj& i : block->block()->elements()) { debug_ast(i, ind + " ", env); } - } else if (Cast(node)) { + // std::cerr << " [" << block->keyword() << "] " << block->tabs(); + std::cerr << std::endl; + // debug_ast(block->interpolation(), ind + "#"); + // debug_ast(block->value(), ind + "+"); + + debug_ast(block->name(), ind + "#"); + debug_ast(block->value(), ind + "="); + + for (const StatementObj& i : block->elements()) { debug_ast(i, ind + " "); } + } + else if (Cast(node)) { EachRule* block = Cast(node); - std::cerr << ind << "EachRule " << block; + std::cerr << ind << "EachRule [" << debug_vec(block->variables()) << "]" << block; std::cerr << " (" << pstate_source_position(node) << ")"; std::cerr << " " << block->tabs() << std::endl; - if (block->block()) for(const Statement_Obj& i : block->block()->elements()) { debug_ast(i, ind + " ", env); } - } else if (Cast(node)) { + for (const StatementObj& i : block->elements()) { debug_ast(i, ind + " "); } + } + else if (Cast(node)) { ForRule* block = Cast(node); std::cerr << ind << "ForRule " << block; std::cerr << " (" << pstate_source_position(node) << ")"; std::cerr << " " << block->tabs() << std::endl; - if (block->block()) for(const Statement_Obj& i : block->block()->elements()) { debug_ast(i, ind + " ", env); } - } else if (Cast(node)) { + for (const StatementObj& i : block->elements()) { debug_ast(i, ind + " "); } + } + else if (Cast(node)) { WhileRule* block = Cast(node); std::cerr << ind << "WhileRule " << block; std::cerr << " (" << pstate_source_position(node) << ")"; std::cerr << " " << block->tabs() << std::endl; - if (block->block()) for(const Statement_Obj& i : block->block()->elements()) { debug_ast(i, ind + " ", env); } - } else if (Cast(node)) { - Definition* block = Cast(node); - std::cerr << ind << "Definition " << block; - std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << " [name: " << block->name() << "] "; - std::cerr << " [type: " << (block->type() == Sass::Definition::Type::MIXIN ? "Mixin " : "Function ") << "] "; - // this seems to lead to segfaults some times? - // std::cerr << " [signature: " << block->signature() << "] "; - std::cerr << " [native: " << block->native_function() << "] "; - std::cerr << " " << block->tabs() << std::endl; - debug_ast(block->parameters(), ind + " params: ", env); - if (block->block()) debug_ast(block->block(), ind + " ", env); - } else if (Cast(node)) { - Mixin_Call* block = Cast(node); - std::cerr << ind << "Mixin_Call " << block << " " << block->tabs(); - std::cerr << " (" << pstate_source_position(block) << ")"; - std::cerr << " [" << block->name() << "]"; - std::cerr << " [has_content: " << block->has_content() << "] " << std::endl; - debug_ast(block->arguments(), ind + " args: ", env); - debug_ast(block->block_parameters(), ind + " block_params: ", env); - if (block->block()) debug_ast(block->block(), ind + " ", env); - } else if (StyleRule* ruleset = Cast(node)) { + debug_ast(block->condition(), ind + " ?? "); + for (const StatementObj& i : block->elements()) { debug_ast(i, ind + " "); } + } + + + else if (ItplFnExpression* ruleset = Cast(node)) { + std::cerr << ind << "PlainCssCallable " << ruleset; + std::cerr << " (" << pstate_source_position(node) << ")"; + // std::cerr << " [indent: " << ruleset->tabs() << "]"; + // std::cerr << " [" << ruleset->name() << "]"; + std::cerr << std::endl; + // debug_ast(ruleset->content(), ind + " @ "); + } + else if (IncludeRule* ruleset = Cast(node)) { + std::cerr << ind << "IncludeRule " << ruleset; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " [indent: " << ruleset->tabs() << "]"; + // std::cerr << " [" << ruleset->name() << "]"; + std::cerr << std::endl; + debug_ast(ruleset->content(), ind + " @ "); + } + else if (ContentBlock* ruleset = Cast(node)) { + std::cerr << ind << "ContentBlock " << ruleset; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " [indent: " << ruleset->tabs() << "]"; + std::cerr << " [" << ruleset->name().orig() << "]"; + std::cerr << std::endl; + debug_block(ruleset, ind + " "); + } + + else if (MixinRule* ruleset = Cast(node)) { + std::cerr << ind << "MixinRule " << ruleset; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " [indent: " << ruleset->tabs() << "]"; + std::cerr << " [" << ruleset->name().orig() << "]"; + std::cerr << std::endl; + debug_ast(ruleset->arguments(), ind + "$"); + debug_block(ruleset, ind + " "); + } + else if (CallableSignature* args = Cast(node)) { + std::cerr << ind << "MixinRArgumentDeclarationule " << args; + std::cerr << " (" << pstate_source_position(node) << ")"; + // std::cerr << " [indent: " << ruleset->tabs() << "]"; + // std::cerr << " [" << ruleset->name() << "]"; + std::cerr << std::endl; + // debug_ast(ruleset->arguments(), ind + "$"); + // debug_ast(ruleset, ind + " "); + } + + else if (FunctionRule* ruleset = Cast(node)) { + std::cerr << ind << "FunctionRule " << ruleset; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " [indent: " << ruleset->tabs() << "]"; + std::cerr << " fidx(" << ruleset->fidx().toString() << ")"; + std::cerr << " [" << ruleset->name().orig() << "]"; + std::cerr << std::endl; + debug_block(ruleset, ind + " "); + } + else if (StyleRule* ruleset = Cast(node)) { std::cerr << ind << "StyleRule " << ruleset; std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " [" << parent_block(ruleset->idxs) << "]"; std::cerr << " [indent: " << ruleset->tabs() << "]"; - std::cerr << (ruleset->is_invisible() ? " [INVISIBLE]" : ""); - std::cerr << (ruleset->is_root() ? " [root]" : ""); + std::cerr << std::endl; + debug_ast(ruleset->interpolation(), ind + "#"); + debug_block(ruleset, ind + " "); + } + else if (CssStyleRule* ruleset = Cast(node)) { + std::cerr << ind << "CssStyleRule " << ruleset; + std::cerr << " (" << pstate_source_position(node) << ")"; std::cerr << std::endl; debug_ast(ruleset->selector(), ind + ">"); - debug_ast(ruleset->block(), ind + " "); - } else if (Cast(node)) { - Block* block = Cast(node); - std::cerr << ind << "Block " << block; - std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << (block->is_invisible() ? " [INVISIBLE]" : ""); - std::cerr << " [indent: " << block->tabs() << "]" << std::endl; - for(const Statement_Obj& i : block->elements()) { debug_ast(i, ind + " ", env); } - } else if (Cast(node)) { - Variable* expression = Cast(node); - std::cerr << ind << "Variable " << expression; - std::cerr << " [interpolant: " << expression->is_interpolant() << "] "; - std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << " [" << expression->name() << "]" << std::endl; - sass::string name(expression->name()); - if (env && env->has(name)) debug_ast(Cast((*env)[name]), ind + " -> ", env); - } else if (Cast(node)) { - Function_Call* expression = Cast(node); - std::cerr << ind << "Function_Call " << expression; - std::cerr << " [interpolant: " << expression->is_interpolant() << "] "; - std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << " [" << expression->name() << "]"; - if (expression->is_delayed()) std::cerr << " [delayed]"; - if (expression->is_interpolant()) std::cerr << " [interpolant]"; - if (expression->is_css()) std::cerr << " [css]"; - std::cerr << std::endl; - debug_ast(expression->arguments(), ind + " args: ", env); - debug_ast(expression->func(), ind + " func: ", env); - } else if (Cast(node)) { - Function* expression = Cast(node); - std::cerr << ind << "Function " << expression; - std::cerr << " (" << pstate_source_position(node) << ")"; - if (expression->is_css()) std::cerr << " [css]"; - std::cerr << std::endl; - debug_ast(expression->definition(), ind + " definition: ", env); - } else if (Cast(node)) { - Arguments* expression = Cast(node); - std::cerr << ind << "Arguments " << expression; - if (expression->is_delayed()) std::cerr << " [delayed]"; - std::cerr << " (" << pstate_source_position(node) << ")"; - if (expression->has_named_arguments()) std::cerr << " [has_named_arguments]"; - if (expression->has_rest_argument()) std::cerr << " [has_rest_argument]"; - if (expression->has_keyword_argument()) std::cerr << " [has_keyword_argument]"; - std::cerr << std::endl; - for(const Argument_Obj& i : expression->elements()) { debug_ast(i, ind + " ", env); } - } else if (Cast(node)) { + // debug_ast(ruleset->interpolation(), ind + "#"); + for (auto stmt : ruleset->elements()) { + debug_ast(stmt, ind + " !! "); + } + // debug_ast(ruleset, ind + " :: "); + } + else if (Cast(node)) { + VariableExpression* expression = Cast(node); + std::cerr << ind << "VariableExpression " << expression; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " [" << expression->name().orig() << "]"; + std::cerr << " vidx("; + bool join = false; + for (auto pidx : expression->vidxs()) { + if (join) std::cerr << ", "; + std::cerr << pidx.toString(); + join = true; + } + std::cerr << ")" << std::endl; + + // sass::string name(expression->name()); + // if (env && env->has(name)) debug_ast(Cast((*env)[name]), ind + " -> "); + } + else if (Cast(node)) { + CallableArguments* arguments = Cast(node); + std::cerr << ind << "CallableArguments " << arguments; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << std::endl; + // std::cerr << ind << " positional: " << debug_vec(arguments->positional()) << "\n"; + std::cerr << ind << " named: " << arguments->named().size() << "\n"; + // std::cerr << ind << " restArg: " << debug_vec(arguments->restArg()) << "\n"; + // std::cerr << ind << " kwdRest: " << debug_vec(arguments->kwdRest()) << "\n"; + + } + else if (Cast(node)) { + FunctionExpression* expression = Cast(node); + std::cerr << ind << "FunctionExpression " << expression; + std::cerr << " (" << pstate_source_position(node) << ")"; + // if (expression->is_css()) std::cerr << " [css]"; + std::cerr << std::endl; + // debug_ast(expression->name(), ind + " name: "); + debug_ast(expression->arguments(), ind + " args: "); + // debug_ast(expression->func(), ind + " func: "); + } + else if (Cast(node)) { Argument* expression = Cast(node); std::cerr << ind << "Argument " << expression; std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << " [" << expression->value().ptr() << "]"; - std::cerr << " [name: " << expression->name() << "] "; + std::cerr << " [" << expression->defval().ptr() << "]"; + std::cerr << " [name: " << expression->name().orig() << "] "; std::cerr << " [rest: " << expression->is_rest_argument() << "] "; std::cerr << " [keyword: " << expression->is_keyword_argument() << "] " << std::endl; - debug_ast(expression->value(), ind + " value: ", env); - } else if (Cast(node)) { - Parameters* expression = Cast(node); - std::cerr << ind << "Parameters " << expression; - std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << " [has_optional: " << expression->has_optional_parameters() << "] "; - std::cerr << " [has_rest: " << expression->has_rest_parameter() << "] "; - std::cerr << std::endl; - for(const Parameter_Obj& i : expression->elements()) { debug_ast(i, ind + " ", env); } - } else if (Cast(node)) { - Parameter* expression = Cast(node); - std::cerr << ind << "Parameter " << expression; - std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << " [name: " << expression->name() << "] "; - std::cerr << " [default: " << expression->default_value().ptr() << "] "; - std::cerr << " [rest: " << expression->is_rest_parameter() << "] " << std::endl; - } else if (Cast(node)) { - Unary_Expression* expression = Cast(node); - std::cerr << ind << "Unary_Expression " << expression; - std::cerr << " [interpolant: " << expression->is_interpolant() << "] "; - std::cerr << " [delayed: " << expression->is_delayed() << "] "; - std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << " [" << expression->type() << "]" << std::endl; - debug_ast(expression->operand(), ind + " operand: ", env); - } else if (Cast(node)) { - Binary_Expression* expression = Cast(node); - std::cerr << ind << "Binary_Expression " << expression; - if (expression->is_interpolant()) std::cerr << " [is interpolant] "; - if (expression->is_left_interpolant()) std::cerr << " [left interpolant] "; - if (expression->is_right_interpolant()) std::cerr << " [right interpolant] "; - std::cerr << " [delayed: " << expression->is_delayed() << "] "; - std::cerr << " [ws_before: " << expression->op().ws_before << "] "; - std::cerr << " [ws_after: " << expression->op().ws_after << "] "; - std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << " [" << expression->type_name() << "]" << std::endl; - debug_ast(expression->left(), ind + " left: ", env); - debug_ast(expression->right(), ind + " right: ", env); - } else if (Cast(node)) { + debug_ast(expression->defval(), ind + " value: "); + } + else if (Cast(node)) { + UnaryOpExpression* expression = Cast(node); + std::cerr << ind << "UnaryOpExpression " << expression; + std::cerr << " (" << pstate_source_position(node) << ")"; + // std::cerr << " [" << expression->type() << "]" << std::endl; + debug_ast(expression->operand(), ind + " operand: "); + } + else if (Cast(node)) { + ParenthesizedExpression* expression = Cast(node); + std::cerr << ind << "ParenthesizedExpression " << expression; + std::cerr << " (" << pstate_source_position(expression) << ")"; + std::cerr << std::endl; + debug_ast(expression->expression(), ind + "() "); + + } + else if (Cast(node)) { + BinaryOpExpression* expression = Cast(node); + std::cerr << ind << "BinaryOpExpression " << expression; + // std::cerr << " [ws_before: " << expression->operand().ws_before << "] "; + // std::cerr << " [ws_after: " << expression->operand().ws_after << "] "; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << std::endl; + debug_ast(expression->left(), ind + " left: "); + debug_ast(expression->right(), ind + " right: "); + } + else if (Cast(node)) { Map* expression = Cast(node); std::cerr << ind << "Map " << expression; - std::cerr << " [interpolant: " << expression->is_interpolant() << "] "; std::cerr << " (" << pstate_source_position(node) << ")"; std::cerr << " [Hashed]" << std::endl; for (const auto& i : expression->elements()) { debug_ast(i.first, ind + " key: "); debug_ast(i.second, ind + " val: "); } - } else if (Cast(node)) { + } + else if (Cast(node)) { + ListExpression* expression = Cast(node); + std::cerr << ind << "ListExpression " << expression; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " (" << expression->size() << ") " << + (expression->separator() == SASS_COMMA ? "Comma " : expression->separator() == SASS_UNDEF ? "Unkonwn" : "Space ") << + " [bracketed: " << expression->hasBrackets() << "] " << + std::endl; + for (size_t i = 0; i < expression->size(); i++) { + debug_ast(expression->get(i), ind + " "); + } + } + else if (Cast(node)) { + MapExpression* expression = Cast(node); + std::cerr << ind << "MapExpression " << expression; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " (" << expression->kvlist().size() << ") " << + std::endl; + for (size_t i = 0; i < expression->kvlist().size(); i++) { + debug_ast(expression->kvlist().at(i), ind + " "); + } + } + else if (Cast(node)) { + + ArgumentList* expression = Cast(node); + std::cerr << ind << "ArgumentList " << expression; + std::cerr << " (" << pstate_source_position(node) << ")"; + std::cerr << " (" << expression->size() << ") " << + (expression->separator() == SASS_COMMA ? "Comma " : expression->separator() == SASS_UNDEF ? "Unkonwn" : "Space ") << + " [bracketed: " << expression->hasBrackets() << "] " << + " [hash: " << expression->hash() << "] " << + std::endl; + ValueFlatMap keywords = expression->keywords(); + for (const auto& i : expression->elements()) { debug_ast(i, ind + " [] "); } + for (const auto& kv : keywords) { debug_ast(kv.second, ind + " " + kv.first.orig().c_str() + " "); } + } + else if (Cast(node)) { List* expression = Cast(node); std::cerr << ind << "List " << expression; std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << " (" << expression->length() << ") " << - (expression->separator() == SASS_COMMA ? "Comma " : expression->separator() == SASS_HASH ? "Map " : "Space ") << - " [delayed: " << expression->is_delayed() << "] " << - " [interpolant: " << expression->is_interpolant() << "] " << - " [listized: " << expression->from_selector() << "] " << - " [arglist: " << expression->is_arglist() << "] " << - " [bracketed: " << expression->is_bracketed() << "] " << - " [expanded: " << expression->is_expanded() << "] " << + std::cerr << " (" << expression->size() << ") " << + (expression->separator() == SASS_COMMA ? "Comma " : expression->separator() == SASS_UNDEF ? "Unkonwn" : "Space ") << + " [bracketed: " << expression->hasBrackets() << "] " << " [hash: " << expression->hash() << "] " << std::endl; - for(const auto& i : expression->elements()) { debug_ast(i, ind + " ", env); } - } else if (Cast(node)) { + for (const auto& i : expression->elements()) { debug_ast(i, ind + " "); } + } + else if (Cast(node)) { Boolean* expression = Cast(node); std::cerr << ind << "Boolean " << expression; std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << " [interpolant: " << expression->is_interpolant() << "] "; std::cerr << " [" << expression->value() << "]" << std::endl; - } else if (Cast(node)) { - Color_RGBA* expression = Cast(node); + } + else if (Cast(node)) { + ColorRgba* expression = Cast(node); std::cerr << ind << "Color " << expression; std::cerr << " (" << pstate_source_position(node) << ")"; std::cerr << " [name: " << expression->disp() << "] "; - std::cerr << " [delayed: " << expression->is_delayed() << "] "; - std::cerr << " [interpolant: " << expression->is_interpolant() << "] "; - std::cerr << " rgba[" << expression->r() << ":" << expression->g() << ":" << expression->b() << "@" << expression->a() << "]" << std::endl; - } else if (Cast(node)) { - Color_HSLA* expression = Cast(node); + std::cerr << " rgba[" << expression->r() << ":" << expression->g() << ":" << expression->b() << "@" << expression->a() << "]" << std::endl; + } + else if (Cast(node)) { + ColorHsla* expression = Cast(node); std::cerr << ind << "Color " << expression; std::cerr << " (" << pstate_source_position(node) << ")"; std::cerr << " [name: " << expression->disp() << "] "; - std::cerr << " [delayed: " << expression->is_delayed() << "] "; - std::cerr << " [interpolant: " << expression->is_interpolant() << "] "; - std::cerr << " hsla[" << expression->h() << ":" << expression->s() << ":" << expression->l() << "@" << expression->a() << "]" << std::endl; - } else if (Cast(node)) { + std::cerr << " hsla[" << expression->h() << ":" << expression->s() << ":" << expression->l() << "@" << expression->a() << "]" << std::endl; + } + else if (Cast(node)) { Number* expression = Cast(node); std::cerr << ind << "Number " << expression; std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << " [delayed: " << expression->is_delayed() << "] "; - std::cerr << " [interpolant: " << expression->is_interpolant() << "] "; std::cerr << " [" << expression->value() << expression->unit() << "]" << " [hash: " << expression->hash() << "] " << std::endl; - } else if (Cast(node)) { + debug_ast(expression->lhsAsSlash(), ind + "[lhs] "); + debug_ast(expression->rhsAsSlash(), ind + "[rhs] "); + } + else if (Cast(node)) { Null* expression = Cast(node); std::cerr << ind << "Null " << expression; - std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << " [interpolant: " << expression->is_interpolant() << "] " + std::cerr << " (" << pstate_source_position(node) << ")" // " [hash: " << expression->hash() << "] " << std::endl; - } else if (Cast(node)) { - String_Quoted* expression = Cast(node); - std::cerr << ind << "String_Quoted " << expression; - std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << " [" << prettyprint(expression->value()) << "]"; - if (expression->is_delayed()) std::cerr << " [delayed]"; - if (expression->is_interpolant()) std::cerr << " [interpolant]"; - if (expression->quote_mark()) std::cerr << " [quote_mark: " << expression->quote_mark() << "]"; - std::cerr << std::endl; - } else if (Cast(node)) { - String_Constant* expression = Cast(node); - std::cerr << ind << "String_Constant " << expression; - if (expression->concrete_type()) { - std::cerr << " " << expression->concrete_type(); - } + } + else if (Cast(node)) { + StringExpression* expression = Cast(node); + // std::cerr << ind << "StringExpression " << expression; + // std::cerr << " [" << prettyprint(expression->text()) << "]"; + std::cerr << ind << "StringExpression"; std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << " [" << prettyprint(expression->value()) << "]"; - if (expression->is_delayed()) std::cerr << " [delayed]"; - if (expression->is_interpolant()) std::cerr << " [interpolant]"; + if (expression->hasQuotes()) std::cerr << " {quoted}"; std::cerr << std::endl; - } else if (Cast(node)) { - String_Schema* expression = Cast(node); - std::cerr << ind << "String_Schema " << expression; - std::cerr << " (" << pstate_source_position(expression) << ")"; - std::cerr << " " << expression->concrete_type(); + debug_ast(expression->text(), ind + " "); + } + else if (Cast(node)) { + ItplString* expression = Cast(node); + std::cerr << ind << "ItplString " << expression; std::cerr << " (" << pstate_source_position(node) << ")"; - if (expression->css()) std::cerr << " [css]"; - if (expression->is_delayed()) std::cerr << " [delayed]"; - if (expression->is_interpolant()) std::cerr << " [is interpolant]"; - if (expression->has_interpolant()) std::cerr << " [has interpolant]"; - if (expression->is_left_interpolant()) std::cerr << " [left interpolant] "; - if (expression->is_right_interpolant()) std::cerr << " [right interpolant] "; + std::cerr << " [" << prettyprint(expression->text()) << "]"; + std::cerr << std::endl; - for(const auto& i : expression->elements()) { debug_ast(i, ind + " ", env); } - } else if (Cast(node)) { + } + else if (Cast(node)) { String* expression = Cast(node); - std::cerr << ind << "String " << expression; - std::cerr << " " << expression->concrete_type(); + std::cerr << ind << "String "; + if (expression->hasQuotes()) { + std::cerr << "[QUOTED] "; + } + std::cerr << expression; + std::cerr << " " << expression->type(); std::cerr << " (" << pstate_source_position(node) << ")"; - if (expression->is_interpolant()) std::cerr << " [interpolant]"; + std::cerr << " [" << prettyprint(expression->value()) << "]"; + // std::cerr << " <" << prettyprint(expression->pstate().token.ws_before()) << ">" + std::cerr << std::endl; + } + else if (Cast(node)) { + Interpolation* expression = Cast(node); + std::cerr << ind << "Interpolation"; + std::cerr << " (" << pstate_source_position(expression) << ")"; + + // std::cerr << ind << "Interpolation " << expression; + // std::cerr << " " << expression->concrete_type(); + // std::cerr << " (" << pstate_source_position(node) << ")"; std::cerr << std::endl; - } else if (Cast(node)) { + for (const auto i : expression->elements()) { debug_ast(i, ind + " "); } + } + else if (Cast(node)) { Expression* expression = Cast(node); std::cerr << ind << "Expression " << expression; std::cerr << " (" << pstate_source_position(node) << ")"; - switch (expression->concrete_type()) { - case Expression::Type::NONE: std::cerr << " [NONE]"; break; - case Expression::Type::BOOLEAN: std::cerr << " [BOOLEAN]"; break; - case Expression::Type::NUMBER: std::cerr << " [NUMBER]"; break; - case Expression::Type::COLOR: std::cerr << " [COLOR]"; break; - case Expression::Type::STRING: std::cerr << " [STRING]"; break; - case Expression::Type::LIST: std::cerr << " [LIST]"; break; - case Expression::Type::MAP: std::cerr << " [MAP]"; break; - case Expression::Type::SELECTOR: std::cerr << " [SELECTOR]"; break; - case Expression::Type::NULL_VAL: std::cerr << " [NULL_VAL]"; break; - case Expression::Type::C_WARNING: std::cerr << " [C_WARNING]"; break; - case Expression::Type::C_ERROR: std::cerr << " [C_ERROR]"; break; - case Expression::Type::FUNCTION: std::cerr << " [FUNCTION]"; break; - case Expression::Type::NUM_TYPES: std::cerr << " [NUM_TYPES]"; break; - case Expression::Type::VARIABLE: std::cerr << " [VARIABLE]"; break; - case Expression::Type::FUNCTION_VAL: std::cerr << " [FUNCTION_VAL]"; break; - case Expression::Type::PARENT: std::cerr << " [PARENT]"; break; - } + // std::cerr << " [" << expression->type() << "]"; std::cerr << std::endl; - } else if (Cast(node)) { - ParentStatement* parent = Cast(node); - std::cerr << ind << "ParentStatement " << parent; + } + else if (Cast(node)) { + ParentStatement* has_block = Cast(node); + std::cerr << ind << "Has_Block " << has_block; std::cerr << " (" << pstate_source_position(node) << ")"; - std::cerr << " " << parent->tabs() << std::endl; - if (parent->block()) for(const Statement_Obj& i : parent->block()->elements()) { debug_ast(i, ind + " ", env); } - } else if (Cast(node)) { + std::cerr << " " << has_block->tabs() << std::endl; + for (const StatementObj& i : has_block->elements()) { debug_ast(i, ind + " "); } + } + else if (Cast(node)) { Statement* statement = Cast(node); std::cerr << ind << "Statement " << statement; std::cerr << " (" << pstate_source_position(node) << ")"; std::cerr << " " << statement->tabs() << std::endl; } + else if (Cast(node)) { + CssAtRule* rule = Cast(node); + std::cerr << ind << "CssAtRule " << rule; + std::cerr << " (" << pstate_source_position(rule) << ")"; + std::cerr << " [name: " << dbgValStr(rule->name()) << "] "; + std::cerr << " [value: " << dbgValStr(rule->value()) << "] "; + std::cerr << std::endl; + debug_block(rule, ind + " "); + } + else if (Cast(node)) { + CssParentNode* rule = Cast(node); + std::cerr << ind << "CssParentNode " << rule; + std::cerr << " (" << pstate_source_position(rule) << ")"; + std::cerr << std::endl; + debug_block(rule, ind + " "); + } + else if (Cast(node)) { + CssNode* rule = Cast(node); + std::cerr << ind << "CssNode " << rule; + std::cerr << " (" << pstate_source_position(rule) << ")"; + std::cerr << std::endl; + } + else if (Cast(node)) { + Calculation* calc = Cast(node); + std::cerr << ind << "Calculation " << calc; + std::cerr << " (" << pstate_source_position(calc) << ")"; + std::cerr << std::endl; + for (auto asd : calc->arguments()) { + debug_ast(asd, ind + " "); + } + } + else if (Cast(node)) { + CalcOperation* op = Cast(node); + std::cerr << ind << "CalcOperation " << op; + std::cerr << " (" << pstate_source_position(op) << ")"; + std::cerr << std::endl; + debug_ast(op->left(), ind + " lhs: "); + debug_ast(op->right(), ind + " rhs: "); + } + else { + std::cerr << ind << "Undetected" << std::endl; + } if (ind == "") std::cerr << "####################################################################\n"; } /* -inline void debug_ast(const AST_Node* node, sass::string ind = "", Env* env = 0) +inline void debug_ast(const AstNode* node, std::string ind = ""* env = 0) { - debug_ast(const_cast(node), ind, env); + debug_ast(const_cast(node), ind); } */ diff --git a/src/emitter.cpp b/src/emitter.cpp index fa50ef9c9b..ca96d0bcb4 100644 --- a/src/emitter.cpp +++ b/src/emitter.cpp @@ -1,67 +1,104 @@ -// sass.hpp must go before all system headers to get the -// __EXTENSIONS__ fix on Solaris. -#include "sass.hpp" #include "emitter.hpp" -#include "util_string.hpp" -#include "util.hpp" + +#include "charcode.hpp" +#include "character.hpp" +#include "string_utils.hpp" namespace Sass { + // Default constructor + + // Import some namespaces + using namespace Charcode; + using namespace Character; - Emitter::Emitter(struct Sass_Output_Options& opt) - : wbuf(), - opt(opt), + Emitter::Emitter(const OutputOptions& outopt) + : wbuf(outopt.mapopt.mode != SASS_SRCMAP_NONE), + outopt(outopt), indentation(0), scheduled_space(0), scheduled_linefeed(0), + parentheses_opened(false), + force_next_mapping(false), scheduled_delimiter(false), - scheduled_crutch(0), - scheduled_mapping(0), + scheduled_mapping(nullptr), in_custom_property(false), - in_comment(false), - in_wrapped(false), - in_media_block(false), - in_declaration(false), + in_declaration(true), + separators(), in_space_array(false), in_comma_array(false) { } // return buffer as string - sass::string Emitter::get_buffer(void) + sass::string Emitter::get_buffer(bool trim) { - return wbuf.buffer; + finalize(false); // flush stuff + sass::string text(std::move(wbuf.buffer)); + // This potentially makes mappings invalid!? + if (trim) StringUtils::makeTrimmed(text); // fails 4 without? + return text; // Should use RVO } - Sass_Output_Style Emitter::output_style(void) const + enum SassOutputStyle Emitter::output_style(void) const { - return opt.output_style; + return outopt.output_style; } // PROXY METHODS FOR SOURCE MAPS - + void Emitter::add_source_index(size_t idx) - { wbuf.smap.source_index.push_back(idx); } + { + if (wbuf.srcmap) wbuf.srcmap->addSourceIndex(idx); } + + // void Emitter::set_filename(const sass::string& str) + // { if (wbuf.srcmap) wbuf.srcmap->setFile(str); } - sass::string Emitter::render_srcmap(Context &ctx) - { return wbuf.smap.render_srcmap(ctx); } + void Emitter::schedule_mapping(const AstNode* node) + { + scheduled_mapping = node; + } + + void Emitter::add_open_mapping(const AstNode* node, bool optional) + { + flush_schedules(); + if (wbuf.srcmap) { + wbuf.srcmap->addOpenMapping(node, + optional && !force_next_mapping); + force_next_mapping = false; + if (scheduled_mapping) { + wbuf.srcmap->addOpenMapping(scheduled_mapping, false); + scheduled_mapping = nullptr; + } + } + } - void Emitter::set_filename(const sass::string& str) - { wbuf.smap.file = str; } + void Emitter::add_close_mapping(const AstNode* node, bool optional) + { + flush_schedules(); + if (wbuf.srcmap) { + wbuf.srcmap->addCloseMapping(node, + optional && !force_next_mapping); + force_next_mapping = false; + if (scheduled_mapping) { + wbuf.srcmap->addCloseMapping(scheduled_mapping, false); + scheduled_mapping = nullptr; + } + } + } - void Emitter::schedule_mapping(const AST_Node* node) - { scheduled_mapping = node; } - void Emitter::add_open_mapping(const AST_Node* node) - { wbuf.smap.add_open_mapping(node); } - void Emitter::add_close_mapping(const AST_Node* node) - { wbuf.smap.add_close_mapping(node); } - SourceSpan Emitter::remap(const SourceSpan& pstate) - { return wbuf.smap.remap(pstate); } + void Emitter::move_next_mapping(int start, int end) + { + flush_schedules(); + if (wbuf.srcmap) { + wbuf.srcmap->moveNextMapping(start, end); + } + } // MAIN BUFFER MANIPULATION // add outstanding delimiter void Emitter::finalize(bool final) { - scheduled_space = 0; + scheduled_space = false; if (output_style() == SASS_STYLE_COMPRESSED) if (final) scheduled_delimiter = false; if (scheduled_linefeed) @@ -77,26 +114,35 @@ namespace Sass { sass::string linefeeds = ""; for (size_t i = 0; i < scheduled_linefeed; i++) - linefeeds += opt.linefeed; - scheduled_space = 0; + linefeeds += outopt.linefeed; + scheduled_space = false; scheduled_linefeed = 0; - append_string(linefeeds); + if (scheduled_delimiter) { + scheduled_delimiter = false; + write_char(';'); + } + write_string(linefeeds); - } else if (scheduled_space) { + } + else if (scheduled_space) { sass::string spaces(scheduled_space, ' '); - scheduled_space = 0; - append_string(spaces); + scheduled_space = false; + if (scheduled_delimiter) { + scheduled_delimiter = false; + write_char(';'); + } + write_string(spaces); } - if (scheduled_delimiter) { + else if (scheduled_delimiter) { scheduled_delimiter = false; - append_string(";"); + write_char(';'); } } // prepend some text or token to the buffer void Emitter::prepend_output(const OutputBuffer& output) { - wbuf.smap.prepend(output); + if (wbuf.srcmap) wbuf.srcmap->prepend(output); wbuf.buffer = output.buffer + wbuf.buffer; } @@ -106,7 +152,7 @@ namespace Sass { // do not adjust mappings for utf8 bom // seems they are not counted in any UA if (text.compare("\xEF\xBB\xBF") != 0) { - wbuf.smap.prepend(Offset(text)); + if (wbuf.srcmap) wbuf.srcmap->prepend(Offset(text)); } wbuf.buffer = text + wbuf.buffer; } @@ -117,119 +163,150 @@ namespace Sass { } // append a single char to the buffer - void Emitter::append_char(const char chr) + void Emitter::append_char(uint8_t chr) { // write space/lf flush_schedules(); + parentheses_opened = false; + // add to buffer + wbuf.buffer.push_back((unsigned char) chr); + // account for data in source-maps + if (wbuf.srcmap) wbuf.srcmap->append(Offset(chr)); + } + + // append a single char to the buffer + void Emitter::write_char(uint8_t chr) + { + parentheses_opened = false; // add to buffer - wbuf.buffer += chr; + wbuf.buffer.push_back((unsigned char)chr); // account for data in source-maps - wbuf.smap.append(Offset(chr)); + if (wbuf.srcmap) wbuf.srcmap->append(Offset(chr)); } // append some text or token to the buffer void Emitter::append_string(const sass::string& text) { - // write space/lf flush_schedules(); + if (text.empty() == false) + parentheses_opened = false; + // add to buffer + wbuf.buffer.append(text); + // account for data in source-maps + if (wbuf.srcmap) wbuf.srcmap->append(Offset(text)); + } - if (in_comment) { - sass::string out = Util::normalize_newlines(text); - if (output_style() == COMPACT) { - out = comment_to_compact_string(out); - } - wbuf.smap.append(Offset(out)); - wbuf.buffer += std::move(out); - } else { - // add to buffer - wbuf.buffer += text; - // account for data in source-maps - wbuf.smap.append(Offset(text)); - } + // append some text or token to the buffer + void Emitter::write_string(const sass::string& text) + { + if (text.empty() == false) + parentheses_opened = false; + // add to buffer + wbuf.buffer.append(text); + // account for data in source-maps + if (wbuf.srcmap) wbuf.srcmap->append(Offset(text)); } - // append some white-space only text - void Emitter::append_wspace(const sass::string& text) + // append some text or token to the buffer + void Emitter::append_string(const sass::string& text, size_t repeat) { - if (text.empty()) return; - if (peek_linefeed(text.c_str())) { - scheduled_space = 0; - append_mandatory_linefeed(); + // write space/lf + flush_schedules(); + if (text.empty() == false) + parentheses_opened = false; + // add to buffer + // wbuf.buffer.append(text, repeat); + for (size_t i = 0; i < repeat; i += 1) { + wbuf.buffer.append(text); } + // account for data in source-maps + if (wbuf.srcmap) wbuf.srcmap->append(Offset(text) * (uint32_t)repeat); } // append some text or token to the buffer - // this adds source-mappings for node start and end - void Emitter::append_token(const sass::string& text, const AST_Node* node) + void Emitter::append_string(const char* text, size_t repeat) { + // write space/lf flush_schedules(); - add_open_mapping(node); - // hotfix for browser issues - // this is pretty ugly indeed - if (scheduled_crutch) { - add_open_mapping(scheduled_crutch); - scheduled_crutch = 0; + // add to buffer + // wbuf.buffer.append(text, repeat); + for (size_t i = 0; i < repeat; i += 1) { + wbuf.buffer.append(text); } - append_string(text); - add_close_mapping(node); + // account for data in source-maps + if (wbuf.srcmap) wbuf.srcmap->append(Offset(text) * (uint32_t)repeat); + } + + // append some text or token to the buffer + // this adds source-mappings for node start and end + void Emitter::append_token(const sass::string& text, const AstNode* node) + { + add_open_mapping(node, true); + write_string(text); + add_close_mapping(node, true); } // HELPER METHODS void Emitter::append_indentation() { - if (output_style() == COMPRESSED) return; - if (output_style() == COMPACT) return; + if (output_style() == SASS_STYLE_COMPRESSED) return; + if (output_style() == SASS_STYLE_COMPACT) return; if (in_declaration && in_comma_array) return; if (scheduled_linefeed && indentation) scheduled_linefeed = 1; - sass::string indent = ""; - for (size_t i = 0; i < indentation; i++) - indent += opt.indent; - append_string(indent); + append_string(outopt.indent, indentation); // 1.5% (realloc) } void Emitter::append_delimiter() { scheduled_delimiter = true; - if (output_style() == COMPACT) { + if (output_style() == SASS_STYLE_COMPACT) { if (indentation == 0) { append_mandatory_linefeed(); } else { append_mandatory_space(); } - } else if (output_style() != COMPRESSED) { + } else if (output_style() != SASS_STYLE_COMPRESSED) { append_optional_linefeed(); } } void Emitter::append_comma_separator() { - // scheduled_space = 0; - append_string(","); + scheduled_space = false; + append_char(','); append_optional_space(); } void Emitter::append_colon_separator() { - scheduled_space = 0; - append_string(":"); - if (!in_custom_property) append_optional_space(); + scheduled_space = false; + append_char(':'); + if (!in_custom_property) { + append_optional_space(); + } } void Emitter::append_mandatory_space() { - scheduled_space = 1; + if (buffer().empty()) { + scheduled_space = true; + } + else if (!isspace(buffer().back())) { + scheduled_space = true; + } } void Emitter::append_optional_space() { - if ((output_style() != COMPRESSED) && buffer().size()) { - unsigned char lst = buffer().at(buffer().length() - 1); - if (!isspace(lst) || scheduled_delimiter) { - if (last_char() != '(') { - append_mandatory_space(); + if ((output_style() != SASS_STYLE_COMPRESSED) && wbuf.buffer.size()) { + if (scheduled_delimiter || !isspace(buffer().back())) { + //if (buffer().back() != $lparen) // breaks escape sequences + { + if (!parentheses_opened) + append_mandatory_space(); } } } @@ -237,17 +314,17 @@ namespace Sass { void Emitter::append_special_linefeed() { - if (output_style() == COMPACT) { + if (output_style() == SASS_STYLE_COMPACT) { append_mandatory_linefeed(); for (size_t p = 0; p < indentation; p++) - append_string(opt.indent); + append_string(outopt.indent); } } void Emitter::append_optional_linefeed() { if (in_declaration && in_comma_array) return; - if (output_style() == COMPACT) { + if (output_style() == SASS_STYLE_COMPACT) { append_mandatory_space(); } else { append_mandatory_linefeed(); @@ -256,41 +333,50 @@ namespace Sass { void Emitter::append_mandatory_linefeed() { - if (output_style() != COMPRESSED) { + if (output_style() != SASS_STYLE_COMPRESSED) { scheduled_linefeed = 1; - scheduled_space = 0; - // flush_schedules(); + scheduled_space = false; } } - void Emitter::append_scope_opener(AST_Node* node) + void Emitter::append_scope_opener(AstNode* node) { scheduled_linefeed = 0; append_optional_space(); flush_schedules(); - if (node) add_open_mapping(node); - append_string("{"); + if (node) add_open_mapping(node, true); + write_char('{'); append_optional_linefeed(); - // append_optional_space(); ++ indentation; } - void Emitter::append_scope_closer(AST_Node* node) + void Emitter::append_scope_closer(AstNode* node) { -- indentation; scheduled_linefeed = 0; - if (output_style() == COMPRESSED) - scheduled_delimiter = false; - if (output_style() == EXPANDED) { - append_optional_linefeed(); - append_indentation(); - } else { - append_optional_space(); + if (last_char() == '{') { + scheduled_space = false; + scheduled_linefeed = 0; + } + else { + if (output_style() == SASS_STYLE_COMPRESSED) + scheduled_delimiter = false; + if (output_style() == SASS_STYLE_EXPANDED) { + append_optional_linefeed(); + append_indentation(); + } + else { + append_optional_space(); + } + } + + append_char('}'); + if (node) { + move_next_mapping(-1, -1); + add_close_mapping(node, true); } - append_string("}"); - if (node) add_close_mapping(node); append_optional_linefeed(); if (indentation != 0) return; - if (output_style() != COMPRESSED) + if (output_style() != SASS_STYLE_COMPRESSED) scheduled_linefeed = 2; } diff --git a/src/emitter.hpp b/src/emitter.hpp index 1a2a9d0e1b..ede1b85cac 100644 --- a/src/emitter.hpp +++ b/src/emitter.hpp @@ -1,67 +1,64 @@ -#ifndef SASS_EMITTER_H -#define SASS_EMITTER_H +#ifndef SASS_EMITTER_HPP +#define SASS_EMITTER_HPP -// sass.hpp must go before all system headers to get the -// __EXTENSIONS__ fix on Solaris. -#include "sass.hpp" +// sass.hpp must go before all system headers +// to get the __EXTENSIONS__ fix on Solaris. +#include "capi_sass.hpp" #include "sass/base.h" +#include "sass/values.h" #include "source_map.hpp" #include "ast_fwd_decl.hpp" namespace Sass { - class Context; class Emitter { public: - Emitter(struct Sass_Output_Options& opt); - virtual ~Emitter() { } + Emitter(const OutputOptions& outopt); protected: OutputBuffer wbuf; public: + void reserve(size_t bytes) { + wbuf.buffer.reserve(bytes); + if (wbuf.srcmap) wbuf.srcmap->reserve(bytes / 20); + } const sass::string& buffer(void) { return wbuf.buffer; } - const SourceMap smap(void) { return wbuf.smap; } - const OutputBuffer output(void) { return wbuf; } + const OutputBuffer& output(void) { return wbuf; } // proxy methods for source maps void add_source_index(size_t idx); - void set_filename(const sass::string& str); - void add_open_mapping(const AST_Node* node); - void add_close_mapping(const AST_Node* node); - void schedule_mapping(const AST_Node* node); - sass::string render_srcmap(Context &ctx); - SourceSpan remap(const SourceSpan& pstate); + // void set_filename(const sass::string& str); + void add_open_mapping(const AstNode* node, bool optional); + void add_close_mapping(const AstNode* node, bool optional); + void schedule_mapping(const AstNode* node); + void move_next_mapping(int start, int end = 0); public: - struct Sass_Output_Options& opt; + const OutputOptions& outopt; size_t indentation; size_t scheduled_space; size_t scheduled_linefeed; + bool parentheses_opened; + bool force_next_mapping; bool scheduled_delimiter; - const AST_Node* scheduled_crutch; - const AST_Node* scheduled_mapping; + const AstNode* scheduled_mapping; public: // output strings different in custom css properties bool in_custom_property; - // output strings different in comments - bool in_comment; - // selector list does not get linefeeds - bool in_wrapped; - // lists always get a space after delimiter - bool in_media_block; // nested list must not have parentheses bool in_declaration; // nested lists need parentheses + sass::vector separators; bool in_space_array; bool in_comma_array; public: // return buffer as sass::string - sass::string get_buffer(void); + sass::string get_buffer(bool trim = false); // flush scheduled space/linefeed - Sass_Output_Style output_style(void) const; + enum SassOutputStyle output_style(void) const; // add outstanding linefeed void finalize(bool final = true); // flush scheduled space/linefeed @@ -70,14 +67,16 @@ namespace Sass { void prepend_string(const sass::string& text); void prepend_output(const OutputBuffer& out); // append some text or token to the buffer + void write_string(const sass::string& text); void append_string(const sass::string& text); + void append_string(const char* text, size_t repeat); + void append_string(const sass::string& text, size_t repeat); // append a single character to buffer - void append_char(const char chr); - // append some white-space only text - void append_wspace(const sass::string& text); + void write_char(uint8_t chr); + void append_char(uint8_t chr); // append some text or token to the buffer // this adds source-mappings for node start and end - void append_token(const sass::string& text, const AST_Node* node); + void append_token(const sass::string& text, const AstNode* node); // query last appended character char last_char(); @@ -88,8 +87,8 @@ namespace Sass { void append_special_linefeed(void); void append_optional_linefeed(void); void append_mandatory_linefeed(void); - void append_scope_opener(AST_Node* node = 0); - void append_scope_closer(AST_Node* node = 0); + void append_scope_opener(AstNode* node = 0); + void append_scope_closer(AstNode* node = 0); void append_comma_separator(void); void append_colon_separator(void); void append_delimiter(void); diff --git a/src/emscripten-bug.cpp b/src/emscripten-bug.cpp new file mode 100644 index 0000000000..8e1c36cd10 --- /dev/null +++ b/src/emscripten-bug.cpp @@ -0,0 +1,13 @@ +#include + +#ifdef __cplusplus +extern "C" { +#endif + +void emscripten_bug() { + std::random_device rd; +} + +#ifdef __cplusplus +} +#endif diff --git a/src/environment.cpp b/src/environment.cpp index 86dbda0c74..fb871bcb67 100644 --- a/src/environment.cpp +++ b/src/environment.cpp @@ -1,260 +1,1012 @@ -#include "sass.hpp" -#include "ast.hpp" +#include "fn_meta.hpp" + +#include + +#include "eval.hpp" +#include "compiler.hpp" +#include "exceptions.hpp" +#include "ast_values.hpp" +#include "ast_callables.hpp" +#include "ast_expressions.hpp" +#include "string_utils.hpp" + #include "environment.hpp" +#include "preloader.hpp" + +#include "eval.hpp" +#include "cssize.hpp" +#include "sources.hpp" +#include "compiler.hpp" +#include "stylesheet.hpp" +#include "exceptions.hpp" +#include "ast_values.hpp" +#include "ast_imports.hpp" +#include "ast_selectors.hpp" +#include "ast_callables.hpp" +#include "ast_statements.hpp" +#include "ast_expressions.hpp" +#include "parser_selector.hpp" +#include "parser_media_query.hpp" +#include "parser_keyframe_selector.hpp" + +#include "parser_stylesheet.hpp" + +#include "compiler.hpp" +#include "charcode.hpp" +#include "charcode.hpp" +#include "character.hpp" +#include "color_maps.hpp" +#include "exceptions.hpp" +#include "source_span.hpp" +#include "ast_imports.hpp" +#include "ast_supports.hpp" +#include "ast_statements.hpp" +#include "ast_expressions.hpp" +#include "parser_expression.hpp" + +#include "debugger.hpp" namespace Sass { - template - Environment::Environment(bool is_shadow) - : local_frame_(environment_map()), - parent_(0), is_shadow_(false) - { } - template - Environment::Environment(Environment* env, bool is_shadow) - : local_frame_(environment_map()), - parent_(env), is_shadow_(is_shadow) - { } - template - Environment::Environment(Environment& env, bool is_shadow) - : local_frame_(environment_map()), - parent_(&env), is_shadow_(is_shadow) - { } - - // link parent to create a stack - template - void Environment::link(Environment& env) { parent_ = &env; } - template - void Environment::link(Environment* env) { parent_ = env; } - - // this is used to find the global frame - // which is the second last on the stack - template - bool Environment::is_lexical() const + // Import some namespaces + using namespace Charcode; + using namespace Character; + using namespace StringUtils; + + Value* Eval::visitAssignRule(AssignRule* a) { - return !! parent_ && parent_->parent_; + + // Optimize case where we know to what variable to assign to + // This should potentially increase performance, but real-time + // profiling only show a very minor increase (but keep anyway). + if (a->vidx().isValid()) { + assigne = &compiler.varRoot.getVariable(a->vidx()); + ValueObj result = a->value()->accept(this); + compiler.varRoot.setVariable(a->vidx(), + result, a->is_default()); + assigne = nullptr; + return nullptr; + } + + ValueObj result; + + const EnvKey& vname(a->variable()); + + if (a->is_default()) { + + auto scope = compiler.getCurrentScope(); + + // If we have a config and the variable is already set + // we still overwrite the variable beside being guarded + WithConfigVar* wconf = nullptr; + if (compiler.wconfig && scope->isInternal && a->ns().empty()) { + wconf = wconfig->getCfgVar(vname); + } + if (wconf) { + // Via load-css + if (wconf->value33) { + result = wconf->value33; + } + // Via regular load + else if (wconf->expression44) { + a->value(wconf->expression44); + } + a->is_default(wconf->isGuarded41); + } + } + + // Emit deprecation for new var with global flag + if (a->is_global()) { + + auto rframe = compiler.varRoot.stack[0]; + auto it = rframe->varIdxs.find(a->variable()); + + bool hasVar = false; + + if (it != rframe->varIdxs.end()) { + EnvRef vidx(rframe, it->second); + auto& value = compiler.varRoot.getVariable(vidx); + if (value != nullptr) hasVar = true; + } + + if (hasVar == false) { + // libsass/variable-scoping/defaults-global-null + // This check may not be needed, but we create a + // superfluous variable slot in the scope + for (auto fwds : rframe->forwards) { + auto it = fwds->varIdxs.find(a->variable()); + if (it != fwds->varIdxs.end()) { + EnvRef vidx(it->second); + auto& value = compiler.varRoot.getVariable(vidx); + if (value != nullptr) hasVar = true; + } + + auto fwd = fwds->module->mergedFwdVar.find(a->variable()); + if (fwd != fwds->module->mergedFwdVar.end()) { + EnvRef vidx(fwd->second); + auto& value = compiler.varRoot.getVariable(vidx); + if (value != nullptr) hasVar = true; + } + } + } + + if (hasVar == false) { + + // Check if we are at the global scope + if (compiler.varRoot.isGlobal()) { + logger.addDeprecation( + "As of LibSass 4.1, !global assignments won't be able to declare new variables.\n" + "Since this assignment is at the root of the stylesheet, the !global" + " flag is unnecessary and can safely be removed.", + a->pstate(), Logger::WARN_GLOBAL_ASSIGN); + } + else { + logger.addDeprecation( + "As of LibSass 4.1, !global assignments won't be able to declare new variables.\n" + "Consider adding `$" + a->variable().orig() + ": null` at the root of the stylesheet.", + a->pstate(), Logger::WARN_GLOBAL_ASSIGN); + } + + } + + } + + if (a->ns().empty()) { + + a->vidx(compiler.varRoot.findVarIdx( + a->variable(), a->ns(), a->is_global())); + assigne = &compiler.varRoot.getVariable(a->vidx()); + if (!result) result = a->value()->accept(this); + if (result) result = withoutSlash(result); + compiler.varRoot.setVariable( + a->vidx(), + result, + a->is_default()); + assigne = nullptr; + + } + else { + + EnvRefs* mod = compiler.getCurrentModule(); + + auto it = mod->module->moduse.find(a->ns()); + /* if (it == mod->module->moduse.end()) { + callStackFrame csf(compiler, a->pstate()); + throw Exception::ModuleUnknown(compiler, a->ns()); + } + else */ if (it->second.second && !it->second.second->isCompiled) { + callStackFrame csf(compiler, a->pstate()); + throw Exception::ModuleUnknown(compiler, a->ns()); + } + + if (!result) result = a->value()->accept(this); + if (result) result = withoutSlash(result); + + if (auto frame = compiler.getCurrentScope()) { + a->vidx(frame->setModVar( + a->variable(), a->ns(), + result, + a->is_default(), + a->pstate())); + } + + } + + if (!a->vidx().isValid()) + { + if (compiler.varRoot.stack.back()->hasNameSpace(a->ns())) { + callStackFrame frame(traces, a->pstate()); + throw Exception::RuntimeException(traces, "Undefined variable."); + } + else { + callStackFrame frame(traces, a->pstate()); + throw Exception::ModuleUnknown(traces, a->ns()); + } + } + + return nullptr; } - // only match the real root scope - // there is still a parent around - // not sure what it is actually use for - // I guess we store functions etc. there - template - bool Environment::is_global() const + + + + AssignRule* StylesheetParser::readVariableDeclarationWithoutNamespace( + const sass::string& ns, Offset start) { - return parent_ && ! parent_->parent_; - } - template - environment_map& Environment::local_frame() { - return local_frame_; + sass::string vname(variableName()); + + if (!ns.empty()) { + assertPublicIdentifier(vname, start); + } + + EnvKey name(vname); + + if (plainCss()) { + error("Sass variables aren't allowed in plain CSS.", + scanner.relevantSpanFrom(start)); + } + + scanWhitespace(); + scanner.expectChar($colon); + scanWhitespace(); + + ExpressionObj value = readExpression(); + + bool guarded = false; + bool global = false; + + Offset flagStart(scanner.offset); + while (scanner.scanChar($exclamation)) { + sass::string flag = readIdentifier(); + if (flag == "default") { + guarded = true; + } + else if (flag == "global") { + if (!ns.empty()) { + error("!global isn't allowed for variables in other modules.", + scanner.relevantSpanFrom(flagStart)); + } + global = true; + } + else { + error("Invalid flag name.", + scanner.relevantSpanFrom(flagStart)); + } + + scanWhitespace(); + flagStart = scanner.offset; + } + + expectStatementSeparator("variable declaration"); + + // Skip to optional global scope + EnvRefs* frame = global ? + compiler.varRoot.stack.front() : + compiler.varRoot.stack.back(); + + SourceSpan pstate(scanner.relevantSpanFrom(start)); + + bool hasVar = false; + auto chroot = frame; + while (chroot) { + if (ns.empty()) { + if (chroot->varIdxs.count(name)) { + hasVar = true; + break; + } + } + if (chroot->isImport || chroot->isSemiGlobal) { + chroot = chroot->pscope; + } + else { + break; + } + } + + AssignRule* declaration = SASS_MEMORY_NEW(AssignRule, + scanner.relevantSpanFrom(start), + name, ns, + {}, value, guarded, global); + + if (ns.empty() && !hasVar) { + frame->createVariable(name); + } + + return declaration; } + // EO readVariableDeclarationWithoutNamespace + + // Consumes a mixin declaration. + // [start] should point before the `@`. + MixinRule* StylesheetParser::readMixinRule(Offset start) + { + + EnvRefs* frame = compiler.getCurrentScope(); + + EnvFrame local(compiler, false); + // Create space for optional content callable + // ToDo: check if this can be conditionally done? + local.idxs->createMixin(Keys::contentRule); + // var precedingComment = lastSilentComment; + // lastSilentComment = null; + sass::string name = readIdentifier(); + scanWhitespace(); + + CallableSignatureObj arguments; + if (scanner.peekChar() == $lparen) { + arguments = parseArgumentDeclaration(); + } + else { + // Dart-sass creates this one too + arguments = SASS_MEMORY_NEW(CallableSignature, + scanner.relevantSpan(), sass::vector()); // empty declaration + } + + if (inMixin || inContentBlock) { + error("Mixins may not contain mixin declarations.", + scanner.relevantSpanFrom(start)); + } + else if (inControlDirective) { + error("Mixins may not be declared in control directives.", + scanner.relevantSpanFrom(start)); + } + + scanWhitespace(); + RAII_FLAG(inMixin, true); + RAII_FLAG(mixinHasContent, false); - template - bool Environment::has_local(const sass::string& key) const - { return local_frame_.find(key) != local_frame_.end(); } + while (frame->isImport) frame = frame->pscope; + EnvRef midx = frame->createMixin(name); + MixinRule* rule = withChildren( + &StylesheetParser::readChildStatement, + start, name, arguments, local.idxs); + // Mixins can't be created in loops + // Must be on root, not even in @if + // Therefore this optimization is safe + rule->midx(midx); + // rule->cidx(cidx); + return rule; + } + // EO _mixinRule - template EnvResult - Environment::find_local(const sass::string& key) + // Consumes a function declaration. + // [start] should point before the `@`. + FunctionRule* StylesheetParser::readFunctionRule(Offset start) { - auto end = local_frame_.end(); - auto it = local_frame_.find(key); - return EnvResult(it, it != end); + // Variables should not be hoisted through + EnvRefs* parent = compiler.varRoot.stack.back(); + EnvFrame local(compiler, false); + + // var precedingComment = lastSilentComment; + // lastSilentComment = null; + sass::string name = readIdentifier(); + sass::string normalized(name); + + scanWhitespace(); + + CallableSignatureObj arguments = parseArgumentDeclaration(); + + if (inMixin || inContentBlock) { + error("Mixins may not contain function declarations.", + scanner.relevantSpanFrom(start)); + } + else if (inControlDirective) { + error("Functions may not be declared in control directives.", + scanner.relevantSpanFrom(start)); + } + + sass::string fname(StringUtils::unvendor(name)); + if (fname == "calc" || fname == "element" || fname == "expression" || fname == "url" + || fname == "and" || fname == "or" || fname == "not" || fname == "clamp") { + error("Invalid function name.", + scanner.relevantSpanFrom(start)); + } + + scanWhitespace(); + FunctionRule* rule = withChildren( + &StylesheetParser::readFunctionRuleChild, + start, name, arguments, local.idxs); + // This is the weird parts correspondant + rule->fidx(parent->createFunction(name, true)); + return rule; } + // EO readFunctionRule - template - T& Environment::get_local(const sass::string& key) - { return local_frame_[key]; } + void exposeFiltered( + VidxEnvKeyMap& merged, + VidxEnvKeyMap expose, + const sass::string prefix, + const std::set& filters, + const sass::string& errprefix, + Logger& logger, + bool show) + { + for (auto& idx : expose) { + if (idx.first.isPrivate()) continue; + EnvKey key(prefix + idx.first.orig()); + if (show == (filters.count(key) == 1)) { + auto it = merged.find(key); + if (it == merged.end()) { + merged.insert({ key, idx.second }); + } + else if (idx.second != it->second) { + throw Exception::RuntimeException(logger, + "Two forwarded modules both define a " + + errprefix + key.norm() + "."); + } + } + } + } - template - void Environment::set_local(const sass::string& key, const T& val) + void exposeFiltered( + VidxEnvKeyMap& merged, + VidxEnvKeyMap expose, + const sass::string prefix, + const sass::string& errprefix, + Logger& logger) { - local_frame_[key] = val; + + for (auto& idx : expose) { + if (idx.first.isPrivate()) continue; + EnvKey key(prefix + idx.first.orig()); + auto it = merged.find(key); + if (it == merged.end()) { + merged.insert({ key, idx.second }); + } + else if (idx.second != it->second) { + throw Exception::RuntimeException(logger, + "Two forwarded modules both define a " + + errprefix + key.norm() + "."); + } + } } - template - void Environment::set_local(const sass::string& key, T&& val) + + void mergeForwards( + EnvRefs* idxs, + Module* module, + WithConfig* wconfig, + Logger& logger) { - local_frame_[key] = val; + + // Only should happen if forward was found in root stylesheet + // Doesn't make much sense as there is nowhere to forward to + if (idxs->module != nullptr) { + // This is needed to support double forwarding (ToDo - need filter, order?) + for (auto& entry : idxs->module->mergedFwdVar) { module->mergedFwdVar.insert(entry); } + for (auto& entry : idxs->module->mergedFwdMix) { module->mergedFwdMix.insert(entry); } + for (auto& entry : idxs->module->mergedFwdFn) { module->mergedFwdFn.insert(entry); } + } + + if (wconfig->hasShowFilter) { + exposeFiltered(module->mergedFwdVar, idxs->varIdxs, wconfig->prefix, wconfig->varFilters, "variable named $", logger, true); + exposeFiltered(module->mergedFwdMix, idxs->mixIdxs, wconfig->prefix, wconfig->callFilters, "mixin named ", logger, true); + exposeFiltered(module->mergedFwdFn, idxs->fnIdxs, wconfig->prefix, wconfig->callFilters, "function named ", logger, true); + } + else if (wconfig->hasHideFilter) { + exposeFiltered(module->mergedFwdVar, idxs->varIdxs, wconfig->prefix, wconfig->varFilters, "variable named $", logger, false); + exposeFiltered(module->mergedFwdMix, idxs->mixIdxs, wconfig->prefix, wconfig->callFilters, "mixin named ", logger, false); + exposeFiltered(module->mergedFwdFn, idxs->fnIdxs, wconfig->prefix, wconfig->callFilters, "function named ", logger, false); + } + else { + exposeFiltered(module->mergedFwdVar, idxs->varIdxs, wconfig->prefix, "variable named $", logger); + exposeFiltered(module->mergedFwdMix, idxs->mixIdxs, wconfig->prefix, "mixin named ", logger); + exposeFiltered(module->mergedFwdFn, idxs->fnIdxs, wconfig->prefix, "function named ", logger); + } + } - template - void Environment::del_local(const sass::string& key) - { local_frame_.erase(key); } - template - Environment* Environment::global_env() + Root* Eval::loadModRule(ModRule* rule) { - Environment* cur = this; - while (cur->is_lexical()) { - cur = cur->parent_; + + // May not be defined yet + Module* mod = rule->module32(); + + // Nothing to be done for built-ins + if (mod && mod->isBuiltIn) { + return nullptr; } - return cur; - } - template - bool Environment::has_global(const sass::string& key) - { return global_env()->has(key); } + // Seems already loaded? + if (rule->root47()) { + return rule->root47(); + } - template - T& Environment::get_global(const sass::string& key) - { return (*global_env())[key]; } + RAII_PTR(WithConfig, wconfig, rule); - template - void Environment::set_global(const sass::string& key, const T& val) + auto sheet = loadModule( + rule->prev51(), rule->url()); + rule->module32(sheet); + rule->root47(sheet); + + return sheet; + + } + + Root* Eval::loadModule( + const sass::string& prev, + const sass::string& url, + bool isImport) { - global_env()->local_frame_[key] = val; + + // Resolve final file to load + const ImportRequest request( + url, prev, false); + + // Search for valid imports (e.g. partials) on the file-system + // Returns multiple valid results for ambiguous import path + const sass::vector& resolved( + compiler.findIncludes(request, isImport)); + + // Error if no file to import was found + if (resolved.empty()) { + throw Exception::UnknownImport(compiler); + } + // Error if multiple files to import were found + else if (resolved.size() > 1) { + throw Exception::AmbiguousImports(compiler, resolved); + } + + // This is guaranteed to either load or error out! + ImportObj loaded = compiler.loadImport(resolved[0]); + ImportStackFrame iframe(compiler, loaded); + + sass::string abspath(loaded->getAbsPath()); + auto cached = compiler.sheets.find(abspath); + if (cached != compiler.sheets.end()) { + return cached->second; + } + + // Permeable seems to have minor negative impact!? + EnvFrame local(compiler, false, true, isImport); // correct + Root* sheet = compiler.registerImport(loaded); + sheet->idxs = local.idxs; + sheet->import = loaded; + return sheet; } - template - void Environment::set_global(const sass::string& key, T&& val) + + + Root* Eval::resolveIncludeImport(IncludeImport* rule) { - global_env()->local_frame_[key] = val; + // Seems already loaded? + if (rule->root47()) { + return rule->root47(); + } + + //if (rule->module32() && rule->module32()->isBuiltIn) { + // return nullptr; + //} + + if (Root* sheet2 = loadModule( + rule->prev51(), + rule->url(), + true + )) { + rule->module32(sheet2); + rule->root47(sheet2); + return sheet2; + } + + return nullptr; + } - template - void Environment::del_global(const sass::string& key) - { global_env()->local_frame_.erase(key); } - template - Environment* Environment::lexical_env(const sass::string& key) + + EnvRefs* Eval::pudding(EnvRefs* idxs, bool intoRoot, EnvRefs* modFrame) { - Environment* cur = this; - while (cur) { - if (cur->has_local(key)) { - return cur; + + if (intoRoot) { + + // Check if we push the same stuff twice + for (auto fwd : modFrame->forwards) { + + // if (idxs == fwd) continue; + + // Checked, needed + for (auto& var : idxs->varIdxs) { + auto it = fwd->varIdxs.find(var.first); + if (it != fwd->varIdxs.end()) { + if (var.second == it->second) continue; + throw Exception::ParserException(compiler, + "$" + var.first.norm() + " is available " + "from multiple global modules."); + } + } + // Checked, needed + for (auto& var : idxs->mixIdxs) { + auto it = fwd->mixIdxs.find(var.first); + if (it != fwd->mixIdxs.end()) { + if (var.second == it->second) continue; + throw Exception::ParserException(compiler, + "Mixin \"" + var.first.norm() + "(...)\" is " + "available from multiple global modules."); + } + } + // Checked, needed + for (auto& var : idxs->fnIdxs) { + auto it = fwd->fnIdxs.find(var.first); + if (it != fwd->fnIdxs.end()) { + if (var.second == it->second) continue; + throw Exception::ParserException(compiler, + "Function \"" + var.first.norm() + "(...)\" is " + "available from multiple global modules."); + } + } + } + + } + else { + + // No idea why this is needed! + for (auto& var : modFrame->varIdxs) { + idxs->module->mergedFwdVar.insert(var); } - cur = cur->parent_; + //for (auto& var : modFrame->mixIdxs) { + // idxs->mixIdxs.insert(var); + //} + //for (auto var : modFrame->fnIdxs) { + // idxs->fnIdxs.insert(var); + //} + } - return this; + + // Expose what has been forwarded to us + // for (auto var : root->mergedFwdVar) { + // if (!var.first.isPrivate()) + // idxs->varIdxs.insert(var); + // } + // for (auto mix : root->mergedFwdMix) { + // if (!mix.first.isPrivate()) + // idxs->mixIdxs.insert(mix); + // } + // for (auto fn : root->mergedFwdFn) { + // if (!fn.first.isPrivate()) + // idxs->fnIdxs.insert(fn); + // } + + return idxs; + } - // see if we have a lexical variable - // move down the stack but stop before we - // reach the global frame (is not included) - template - bool Environment::has_lexical(const sass::string& key) const + void Eval::insertModule(Module* module, bool clone) { - auto cur = this; - while (cur->is_lexical()) { - if (cur->has_local(key)) return true; - cur = cur->parent_; + // Nowhere to append to, exit + if (current == nullptr) return; + // Nothing to be added yet? Error? + if (module->compiled == nullptr) return; + + // std::cerr << "Insert module " << modctx42->import->getImpPath() << "\n"; + + // For imports we always reproduce +// if (inImport) { +// // Don't like the recursion, but OK +// for (auto upstream : module->upstream) { +// insertModule(upstream); +// } +// } + + // The children to be added to the document + auto children(module->compiled->elements()); + if (inImport) { + //for (auto& child : children) { + // child = child->produce(); + //} + // module->compiled->elements() = children; + } + + if (clone) { + } + + // Check if we have any parent + // Meaning we append to the root + if (!current->parent()) { + // CssNodeVector copy; + for (CssNode* child : children) { + // if (inImport81) { + // debug_ast(child); + // } + if (auto css2 = child->isaCssStyleRule()) { + if (clone) { + // std::cerr << "Cloning for import\n"; + } + auto css = clone ? css2->produce() : css2; + // if (!css->selector()->hasPlaceholder()) { + extender2->_registerSelector(css->selector(), css->selector(), true); + // } + current->append(css); + }else + { + current->append(child); + } + // copy.push_back(child); + } + // current->concat(children); + return; + } + // Process all children to be added + // Each one needs to be interweaved + for (auto& child : children) { + auto css = child->isaCssStyleRule(); + auto parent = current->isaCssStyleRule(); + if (css && parent) { + for (auto& inner : css->elements()) { + // SelectorListObj copy = SASS_MEMORY_COPY(css->selector()); + for (ComplexSelector* selector : css->selector()->elements()) { + selector->chroots(false); + } + SelectorListObj resolved = css->selector()->resolveParentSelectors( + parent->selector(), compiler, true); + extender2->_registerSelector(resolved, resolved, true); + current->parent()->append(SASS_MEMORY_NEW(CssStyleRule, + css->pstate(), current, resolved, { inner })); + } + } + else { + // At rules must be hoisted again... + // Dart seems to use a callback ... + if (child->isaCssAtRule()) { + //current->append(child); + getRoot()->append(child); + } + else { + current->append(child); + } + } } - return false; } - // see if we have a lexical we could update - // either update already existing lexical value - // or if flag is set, we create one if no lexical found - template - void Environment::set_lexical(const sass::string& key, const T& val) + void Eval::compileModule(Root* root) { - Environment* cur = this; - bool shadow = false; - while ((cur && cur->is_lexical()) || shadow) { - EnvResult rv(cur->find_local(key)); - if (rv.found) { - rv.it->second = val; - return; - } - shadow = cur->is_shadow(); - cur = cur->parent_; - } - set_local(key, val); - } - // this one moves the value - template - void Environment::set_lexical(const sass::string& key, T&& val) + + if (root->isCompiled) return; + root->isCompiled = true; + + root->compiled = current; + root->compiled = SASS_MEMORY_NEW(CssStyleRule, + root->pstate(), nullptr, selectorStack.back()); + auto oldCurrent = current; + current = root->compiled; + + // if (modctx42) { + // // Wrong, add to modules? + // modctx42->upstreams.push_back(root); + // } + + RAII_MODULE(modules, root); + RAII_PTR(Root, modctx42, root); + EnvRefs* idxs = root->idxs; + + EnvRefs* mframe(compiler.getCurrentModule()); + + // Make frame scope active for evaluation + EnvScope scoped(compiler.varRoot, idxs); + RAII_PTR(ExtensionStore, extender2, root->extender); + + selectorStack.push_back(nullptr); + for (auto& child : root->elements()) { + child->accept(this); + } + selectorStack.pop_back(); + + current = oldCurrent; + + for (auto& var : mframe->varIdxs) { + ValueObj& slot(compiler.varRoot.getModVar(var.second)); + if (slot == nullptr) slot = SASS_MEMORY_NEW(Null, root->pstate()); + } + + } + + void Eval::exposeFwdRule(ForwardRule* rule) { - Environment* cur = this; - bool shadow = false; - while ((cur && cur->is_lexical()) || shadow) { - EnvResult rv(cur->find_local(key)); - if (rv.found) { - rv.it->second = val; - return; - } - shadow = cur->is_shadow(); - cur = cur->parent_; - } - set_local(key, val); + if (rule->wasExposed()) return; + rule->wasExposed(true); + // if (!rule->module32()) return; + mergeForwards(rule->module32()->idxs, + modctx42, rule, compiler); + } - // look on the full stack for key - // include all scopes available - template - bool Environment::has(const sass::string& key) const + void Eval::exposeUseRule(UseRule* rule) { - auto cur = this; - while (cur) { - if (cur->has_local(key)) { - return true; + + if (rule->wasExposed()) return; + rule->wasExposed(true); + // if (!rule->module32()) return; + + EnvRefs* frame(compiler.getCurrentScope()); + + if (rule->module32()->isBuiltIn) { + + if (rule->ns().empty()) { + frame->forwards.push_back(rule->module32()->idxs); + } + else if (frame->module->moduse.count(rule->ns())) { + throw Exception::ModuleAlreadyKnown(compiler, rule->ns()); + } + else { + frame->module->moduse.insert({ rule->ns(), + { rule->module32()->idxs, nullptr } }); + } + + } + else if (rule->root47()) { + + // Root* root = rule->root47(); + + pudding(rule->root47()->idxs, rule->ns().empty(), frame); + + if (rule->ns().empty()) { + // We should pudding when accessing!? + frame->forwards.push_back(rule->root47()->idxs); + } + else { + // Refactor to only fetch once! + if (frame->module->moduse.count(rule->ns())) { + throw Exception::ModuleAlreadyKnown(compiler, rule->ns()); + } + + std::cerr << "LOADED " << rule->ns() << "\n"; + + frame->module->moduse[rule->ns()] = + { rule->root47()->idxs, rule->root47() }; } - cur = cur->parent_; + + } + else { + + throw "Invalid state!"; + } - return false; + + } - // look on the full stack for key - // include all scopes available - template EnvResult - Environment::find(const sass::string& key) + void Eval::exposeImpRule(IncludeImport* rule) { - auto cur = this; - while (true) { - EnvResult rv(cur->find_local(key)); - if (rv.found) return rv; - cur = cur->parent_; - if (!cur) return rv; - } - }; - - // use array access for getter and setter functions - template - T& Environment::get(const sass::string& key) + + EnvRefs* pframe = compiler.getCurrentScope(); + + while (pframe->isImport) { + pframe = pframe->pscope; + } + + EnvRefs* cidxs = rule->root47()->idxs; + + if (!pframe->isInternal) { + + cidxs->module = rule->root47(); + pframe->forwards.insert( + pframe->forwards.begin(), + rule->root47()->idxs); + + } + else { + + // Merge it up through all imports + for (auto& var : cidxs->varIdxs) { + if (pframe->varIdxs.count(var.first) == 0) { + pframe->createVariable(var.first); + } + } + + // Merge it up through all imports + for (auto& fn : cidxs->fnIdxs) { + if (pframe->fnIdxs.count(fn.first) == 0) { + pframe->createFunction(fn.first, true); + } + } + + // Merge it up through all imports + // for (auto& mix : cidxs->mixIdxs) { + // if (pframe->mixIdxs.count(mix.first) == 0) { + // pframe->createMixin(mix.first); + // } + // } + + // Import to forward + for (auto& var : rule->root47()->mergedFwdVar) { + pframe->varIdxs[var.first] = var.second; + } // a: 18 + for (auto& fn : rule->root47()->mergedFwdFn) { + pframe->fnIdxs[fn.first] = fn.second; + } + for (auto& mix : rule->root47()->mergedFwdMix) { + pframe->mixIdxs[mix.first] = mix.second; + } + + } + + } + // EO exposeImpRule + + // Import shares this environment's variables, + // functions, and mixins, but not its modules. + void Eval::acceptIncludeImport(IncludeImport* rule) { - auto cur = this; - while (cur) { - if (cur->has_local(key)) { - return cur->get_local(key); + BackTrace trace(rule->pstate(), Strings::importRule); + callStackFrame cframe(logger, trace); + if (Root* root = loadModRule(rule)) { + ImportStackFrame iframe(compiler, root->import); + modctx42->upstream.push_back(root); + RAII_MODULE(modules, root); + + // We need to RAII objects + // One to append upstream modules + // Another to add extend selectors + // Only different for import? + + // RAII_PTR(Root, modctx42, root); + RAII_PTR(Root, modctx42, root); + RAII_FLAG(inImport, true); + exposeImpRule(rule); + + // RAII_FLAG(plainCss, root->import->syntax == SASS_IMPORT_CSS); + + // Imports are always executed again + RAII_FLAG(inImport81, true); + // Root is css -> set flags + for (const StatementObj& item : root->elements()) { + item->accept(this); } - cur = cur->parent_; } - return get_local(key); } - // use array access for getter and setter functions - template - T& Environment::operator[](const sass::string& key) + Value* Eval::visitUseRule(UseRule* rule) { - auto cur = this; - while (cur) { - if (cur->has_local(key)) { - return cur->get_local(key); + + BackTrace trace(rule->pstate(), Strings::useRule); + callStackFrame cframe(logger, trace); + // std::cerr << "LOad: " << rule->url() << "\n"; + if (Root* root = loadModRule(rule)) { + std::cerr << "LOADED mod rule\n"; + modctx42->upstream.push_back(root); + if (!root->isCompiled) { + ImportStackFrame iframe(compiler, root->import); + LocalOption scoped(compiler.hasWithConfig, + compiler.hasWithConfig || rule->hasConfig); + RAII_PTR(WithConfig, wconfig, rule); + RAII_PTR(Root, extctx33, root); + RAII_PTR(Root, modctx42, root); + compileModule(root); + // std::cerr << "URL: " << root->import->getAbsPath() << "\n"; + // debug_ast(root->compiled); + rule->finalize(compiler); + + // Only first occurence is inserted + insertModule(root); + } + else if (inImport) { + // We must also produce inner modules somehow + // We must create copies of selectors, after they + // have been extended internally, but we must not + // extend the original selectors of e.g. used modules, + // since they might be re-used in another context where + // these inner changes should not be visible. + RAII_FLAG(inImport, false); + insertModule(root, true); + } + else if (rule->hasConfig) { + throw Exception::ParserException(compiler, + "This module was already loaded, so it " + "can't be configured using \"with\"."); } - cur = cur->parent_; } - return get_local(key); + + std::cerr << "Expose use rule\n"; + exposeUseRule(rule); + return nullptr; } -/* - #ifdef DEBUG - template - size_t Environment::print(sass::string prefix) + + Value* Eval::visitForwardRule(ForwardRule* rule) { - size_t indent = 0; - if (parent_) indent = parent_->print(prefix) + 1; - std::cerr << prefix << sass::string(indent, ' ') << "== " << this << std::endl; - for (typename environment_map::iterator i = local_frame_.begin(); i != local_frame_.end(); ++i) { - if (!ends_with(i->first, "[f]") && !ends_with(i->first, "[f]4") && !ends_with(i->first, "[f]2")) { - std::cerr << prefix << sass::string(indent, ' ') << i->first << " " << i->second; - if (Value* val = Cast(i->second)) - { std::cerr << " : " << val->to_string(); } - std::cerr << std::endl; - } - } - return indent ; + + BackTrace trace(rule->pstate(), Strings::forwardRule, false); + callStackFrame frame333(logger, trace); + + if (Root* root = loadModRule(rule)) { + modctx42->upstream.push_back(root); + if (!root->isCompiled) { + ImportStackFrame iframe(compiler, root->import); + LocalOption scoped(compiler.hasWithConfig, + compiler.hasWithConfig || rule->hasConfig); + RAII_PTR(WithConfig, wconfig, rule); + RAII_PTR(Root, extctx33, root); + compileModule(root); + rule->finalize(compiler); + insertModule(root); + } + else if (compiler.hasWithConfig || rule->hasConfig) { + throw Exception::ParserException(compiler, + "This module was already loaded, so it " + "can't be configured using \"with\"."); + } + } + + exposeFwdRule(rule); + return nullptr; } - #endif -*/ - // compile implementation for AST_Node - template class Environment; } - diff --git a/src/environment.hpp b/src/environment.hpp index 31b0b7401b..ce6620ff93 100644 --- a/src/environment.hpp +++ b/src/environment.hpp @@ -1,123 +1,39 @@ -#ifndef SASS_ENVIRONMENT_H -#define SASS_ENVIRONMENT_H - -// sass.hpp must go before all system headers to get the -// __EXTENSIONS__ fix on Solaris. -#include "sass.hpp" - -#include -#include -#include "ast_fwd_decl.hpp" -#include "ast_def_macros.hpp" - -namespace Sass { - - // this defeats the whole purpose of environment being templatable!! - typedef environment_map::iterator EnvIter; - - class EnvResult { - public: - EnvIter it; - bool found; - public: - EnvResult(EnvIter it, bool found) - : it(it), found(found) {} - }; - - template - class Environment { - // TODO: test with map - environment_map local_frame_; - ADD_PROPERTY(Environment*, parent) - ADD_PROPERTY(bool, is_shadow) - +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#ifndef SASS_ENVIRONMENT_HPP +#define SASS_ENVIRONMENT_HPP + +// sass.hpp must go before all system headers +// to get the __EXTENSIONS__ fix on Solaris. +#include "capi_sass.hpp" + +#include "ast_nodes.hpp" +#include "environment_key.hpp" +#include "environment_cnt.hpp" + +namespace std { + template <> + struct hash { public: - Environment(bool is_shadow = false); - Environment(Environment* env, bool is_shadow = false); - Environment(Environment& env, bool is_shadow = false); - - // link parent to create a stack - void link(Environment& env); - void link(Environment* env); - - // this is used to find the global frame - // which is the second last on the stack - bool is_lexical() const; - - // only match the real root scope - // there is still a parent around - // not sure what it is actually use for - // I guess we store functions etc. there - bool is_global() const; - - // scope operates on the current frame - - environment_map& local_frame(); - - bool has_local(const sass::string& key) const; - - EnvResult find_local(const sass::string& key); - - T& get_local(const sass::string& key); - - // set variable on the current frame - void set_local(const sass::string& key, const T& val); - void set_local(const sass::string& key, T&& val); - - void del_local(const sass::string& key); - - // global operates on the global frame - // which is the second last on the stack - Environment* global_env(); - // get the env where the variable already exists - // if it does not yet exist, we return current env - Environment* lexical_env(const sass::string& key); - - bool has_global(const sass::string& key); - - T& get_global(const sass::string& key); - - // set a variable on the global frame - void set_global(const sass::string& key, const T& val); - void set_global(const sass::string& key, T&& val); - - void del_global(const sass::string& key); - - // see if we have a lexical variable - // move down the stack but stop before we - // reach the global frame (is not included) - bool has_lexical(const sass::string& key) const; - - // see if we have a lexical we could update - // either update already existing lexical value - // or we create a new one on the current frame - void set_lexical(const sass::string& key, T&& val); - void set_lexical(const sass::string& key, const T& val); - - // look on the full stack for key - // include all scopes available - bool has(const sass::string& key) const; - - // look on the full stack for key - // include all scopes available - T& get(const sass::string& key); - - // look on the full stack for key - // include all scopes available - EnvResult find(const sass::string& key); - - // use array access for getter and setter functions - T& operator[](const sass::string& key); - - #ifdef DEBUG - size_t print(sass::string prefix = ""); - #endif - + inline size_t operator()(const Sass::EnvKey& name) const + { + return name.hash(); + } }; +}; + +namespace Sass { - // define typedef for our use case - typedef Environment Env; - typedef sass::vector EnvStack; + void mergeForwards( + EnvRefs* idxs, + Module* module, + bool isShown, + bool isHidden, + const sass::string prefix, + const std::set& toggledVariables, + const std::set& toggledCallables, + Logger& logger); } diff --git a/src/environment_cnt.hpp b/src/environment_cnt.hpp new file mode 100644 index 0000000000..e30c2c2b5f --- /dev/null +++ b/src/environment_cnt.hpp @@ -0,0 +1,59 @@ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#ifndef SASS_ENVIRONMENT_CNT_H +#define SASS_ENVIRONMENT_CNT_H + +// sass.hpp must go before all system headers +// to get the __EXTENSIONS__ fix on Solaris. +#include "capi_sass.hpp" + +#include "flat_map.hpp" +#include "ast_fwd_decl.hpp" +#include "environment_key.hpp" + +namespace Sass { + + template + using EnvKeyMap = UnorderedMap< + const EnvKey, T, hashEnvKey, equalsEnvKey, + Sass::Allocator> + >; + + using EnvKeySet = UnorderedSet< + EnvKey, hashEnvKey, equalsEnvKey, + Sass::Allocator + >; + + template + using ModuleMap = UnorderedMap< + const sass::string, T, hashString, equalsString, + std::allocator> + >; + + using ModuleSet = UnorderedSet< + sass::string, hashString, equalsString, + std::allocator + >; + + template + // Performance comparison on MSVC and bolt-bench: + // tsl::hopscotch_map is 10% slower than Sass::FlatMap + // std::unordered_map a bit faster than tsl::hopscotch_map + // Sass::FlapMap is 10% faster than any other container + // Note: only due to our very specific usage patterns! + using EnvKeyFlatMap = FlatMap; + + typedef sass::vector EnvKeys; + typedef EnvKeyFlatMap ValueFlatMap; + typedef EnvKeyFlatMap ExpressionFlatMap; + + // Helper typedefs to test implementations + // We seem to need order preserving at least for globals + typedef EnvKeyFlatMap VidxEnvKeyMap; + typedef EnvKeyFlatMap MidxEnvKeyMap; + typedef EnvKeyFlatMap FidxEnvKeyMap; + +}; + +#endif diff --git a/src/environment_key.hpp b/src/environment_key.hpp new file mode 100644 index 0000000000..8b3627d5ab --- /dev/null +++ b/src/environment_key.hpp @@ -0,0 +1,161 @@ +#ifndef SASS_ENVIRONMENT_KEY_HPP +#define SASS_ENVIRONMENT_KEY_HPP + +// sass.hpp must go before all system headers +// to get the __EXTENSIONS__ fix on Solaris. +#include "capi_sass.hpp" + +namespace Sass { + + class EnvKey { + + private: + + // The original name + sass::string _orig; + // The normalized name + sass::string _norm; + + // Lazy calculated hash + mutable size_t _hash; + + // Called by constructors + inline void normalize() + { + // Normalize string + std::replace( + _norm.begin(), + _norm.end(), + '_', '-'); + } + + public: + + // Empty constructor + EnvKey() : + _hash(0) + {} + + // String copy constructor + EnvKey(const sass::string& orig) : + _orig(orig), + _norm(orig), + _hash(0) + { + normalize(); + } + + // String move constructor + EnvKey(sass::string&& orig) : + _orig(std::move(orig)), + _norm(_orig), + _hash(0) + { + normalize(); + } + + // Copy constructor + EnvKey(const EnvKey& key) : + _orig(key._orig), + _norm(key._norm), + _hash(key._hash) + { + } + + // Move constructor + EnvKey(EnvKey&& key) noexcept : + _orig(std::move(key._orig)), + _norm(std::move(key._norm)), + _hash(std::move(key._hash)) + { + } + + // Copy assignment operator + EnvKey& operator=(const EnvKey& key) + { + _orig = key._orig; + _norm = key._norm; + _hash = key._hash; + return *this; + } + + // Move assignment operator + EnvKey& operator=(EnvKey&& key) noexcept + { + _orig = std::move(key._orig); + _norm = std::move(key._norm); + _hash = std::move(key._hash); + return *this; + } + + bool isPrivate() const { + return _norm[0] == '-'; + } + + // Compare normalization forms + bool operator==(const EnvKey& rhs) const + { + return norm() == rhs.norm(); + } + + // Compare normalization forms + bool operator<(const EnvKey& rhs) const + { + return norm() < rhs.norm(); + } + + // Simple helper + bool empty() const + { + return _norm.empty(); + } + + // Simple constant getter functions + const sass::string& orig() const { return _orig; } + const sass::string& norm() const { return _norm; } + + // Calculate only on demand + size_t hash() const { + if (_hash == 0) { + _hash = MurmurHash2( + (void*)_norm.c_str(), + (int)_norm.size(), + getHashSeed()); + } + return _hash; + } + + }; + + struct hashEnvKey { + inline size_t operator()(const EnvKey& str) const + { + return str.hash(); + } + }; + + struct equalsEnvKey { + bool operator() (const EnvKey& lhs, const EnvKey& rhs) const { + return lhs.norm() == rhs.norm(); + } + }; + + struct hashString { + inline size_t operator()(const sass::string& str) const + { + return MurmurHash2( + (void*)str.c_str(), + (int)str.size(), + getHashSeed()); + } + }; + + struct equalsString { + bool operator() (const sass::string& lhs, const sass::string& rhs) const { + return lhs == rhs; + } + }; + +}; + +#endif diff --git a/src/environment_stack.cpp b/src/environment_stack.cpp new file mode 100644 index 0000000000..90cfcf976a --- /dev/null +++ b/src/environment_stack.cpp @@ -0,0 +1,647 @@ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#include "environment_stack.hpp" + +#include "ast_expressions.hpp" +#include "ast_statements.hpp" +#include "exceptions.hpp" +#include "compiler.hpp" + +namespace Sass { + + ///////////////////////////////////////////////////////////////////////// + // Each parsed scope gets its own environment frame + ///////////////////////////////////////////////////////////////////////// + + const EnvRef nullidx{ 0xFFFFFFFF }; + + // The root is used for all runtime state + // Also contains parsed root scope stack + EnvRoot::EnvRoot( + Compiler& compiler) : + compiler(compiler), + stack(compiler.varStack3312), + idxs(new EnvRefs( + *this, // root + nullptr,// pscope + false, // isImport + true, // isInternal + false)) // isSemiGlobal + { + varStack.reserve(256); + mixStack.reserve(128); + fnStack.reserve(256); + intVariables.reserve(256); + intMixin.reserve(128); + intFunction.reserve(256); + // Push onto our stack + stack.push_back(this->idxs); + } + // EO EnvRoot ctor + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // Value constructor + EnvFrame::EnvFrame( + Compiler& compiler, + bool isSemiGlobal, + bool isInternal, + bool isImport) : + stack(compiler.varRoot.stack), + idxs(new EnvRefs( + compiler.varRoot, + compiler.varRoot.stack.back(), + isImport, isInternal, isSemiGlobal)) + { + if (isInternal) { + // Lives in built-in scope + idxs->isInternal = true; + } + // Check and prevent stack smashing + if (stack.size() > SassMaxNesting) { + throw Exception::RecursionLimitError(); + } + // Push onto our stack + stack.push_back(this->idxs); + // Account for allocated memory + idxs->root.scopes.push_back(idxs); + } + // EO EnvFrame ctor + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // Remove frame from stack on destruction + EnvFrame::~EnvFrame() + { + // Pop from stack + stack.pop_back(); + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // Register new variable on local stack + // Invoked mostly by stylesheet parser + EnvRef EnvRefs::createVariable( + const EnvKey& name) + { + if (isInternal) { + uint32_t offset = (uint32_t)root.intVariables.size(); + root.intVariables.resize(offset + 1); + varIdxs[name] = offset; + return { offset }; + } + // Get local offset to new variable + uint32_t offset = (uint32_t)varIdxs.size(); + // Remember the variable name + varIdxs[name] = offset; + // Return stack index reference + return { this, offset }; + } + // EO createVariable + + // Register new function on local stack + // Mostly invoked by built-in functions + // Then invoked for custom C-API function + // Finally for every parsed function rule + EnvRef EnvRefs::createFunction( + const EnvKey& name, bool special) + { + if (isInternal) { + uint32_t offset = (uint32_t)root.intFunction.size(); + // ToDo: why is this here, very weird! + // if (!special) + // ToDo: store in n_functions to count + root.intFunction.resize(offset + 1); + // if (offset == 127) std::cerr << "Resized the fucker\n"; + fnIdxs[name] = offset; + return { offset }; + } + // Get local offset to new function + uint32_t offset = (uint32_t)fnIdxs.size(); + // Remember the function name + fnIdxs[name] = offset; + // Return stack index reference + return { this, offset }; + } + // EO createFunction + + // Register new mixin on local stack + // Only invoked for mixin rules + // But also for content blocks + EnvRef EnvRefs::createMixin( + const EnvKey& name) + { + if (isInternal) { + uint32_t offset = (uint32_t)root.intMixin.size(); + root.intMixin.resize(offset + 1); + mixIdxs[name] = offset; + return { offset }; + } + // Get local offset to new mixin + uint32_t offset = (uint32_t)mixIdxs.size(); + // Remember the mixin name + mixIdxs[name] = offset; + // Return stack index reference + return { this, offset }; + } + // EO createMixin + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // Get value instance by stack index reference + // Just converting and returning reference to array offset + ValueObj& EnvRoot::getVariable(const EnvRef& vidx) + { + if (vidx.idxs == nullptr || vidx.idxs->isInternal) { + return intVariables[vidx.offset]; + } + else { + return varStack[vidx.idxs->varOffset + vidx.offset]; + } + } + // EO getVariable + + // Get function instance by stack index reference + // Just converting and returning reference to array offset + ValueObj& EnvRoot::getModVar(const uint32_t offset) + { + return intVariables[offset]; + } + // EO findFunction + + // Get function instance by stack index reference + // Just converting and returning reference to array offset + CallableObj& EnvRoot::getModFn(const uint32_t offset) + { + return intFunction[offset]; + } + // EO findFunction + + // Get function instance by stack index reference + // Just converting and returning reference to array offset + CallableObj& EnvRoot::getModMix(const uint32_t offset) + { + return intMixin[offset]; + } + // EO findFunction + + // Get function instance by stack index reference + // Just converting and returning reference to array offset + CallableObj& EnvRoot::getFunction(const EnvRef& fidx) + { + if (fidx.idxs == nullptr || fidx.idxs->isInternal) { + return intFunction[fidx.offset]; + } + else { + return fnStack[size_t(fidx.idxs->fnOffset) + fidx.offset]; + } + } + // EO findFunction + + // Get mixin instance by stack index reference + // Just converting and returning reference to array offset + CallableObj& EnvRoot::getMixin(const EnvRef& midx) + { + if (midx.idxs == nullptr || midx.idxs->isInternal) { + return intMixin[midx.offset]; + } + else { + return mixStack[size_t(midx.idxs->mixOffset) + midx.offset]; + } + } + // EO getMixin + + void EnvRoot::setModVar(const uint32_t offset, Value* value, bool guarded, const SourceSpan& pstate) + { + if (offset < privateVarOffset) { + callStackFrame frame(compiler, pstate); + throw Exception::RuntimeException(compiler, + "Cannot modify built-in variable."); + } + ValueObj& slot(intVariables[offset]); + if (!guarded || !slot || slot->isaNull()) { + slot = value; + } + } + + // Set items on runtime/evaluation phase via references + // Just converting reference to array offset and assigning + void EnvRoot::setVariable(const EnvRef& vidx, Value* value, bool guarded) + { + if (vidx.idxs == nullptr || vidx.idxs->isInternal) { + ValueObj& slot(intVariables[vidx.offset]); + if (!guarded || !slot || slot->isaNull()) { + slot = value; + } + } + else { + ValueObj& slot(varStack[vidx.idxs->varOffset + vidx.offset]); + if (slot == nullptr || guarded == false) slot = value; + } + } + // EO setVariable + + // Set items on runtime/evaluation phase via references + // Just converting reference to array offset and assigning + void EnvRoot::setFunction(const EnvRef& fidx, UserDefinedCallable* value, bool guarded) + { + if (fidx.idxs == nullptr || fidx.idxs->isInternal) { + if (!guarded || intFunction[fidx.offset] == nullptr) + if (value != nullptr) intFunction[fidx.offset] = value; + } + else { + CallableObj& slot(fnStack[fidx.idxs->fnOffset + fidx.offset]); + if (!guarded || !slot) slot = value; + } + } + // EO setFunction + + // Set items on runtime/evaluation phase via references + // Just converting reference to array offset and assigning + void EnvRoot::setMixin(const EnvRef& midx, UserDefinedCallable* value, bool guarded) + { + if (midx.idxs == nullptr || midx.idxs->isInternal) { + if (!guarded || intMixin[midx.offset] == nullptr) + intMixin[midx.offset] = value; + } + else { + CallableObj& slot(mixStack[midx.idxs->mixOffset + midx.offset]); + if (!guarded || !slot) slot = value; + } + } + // EO setMixin + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // Get a function associated with the under [name]. + // Will lookup from the last runtime stack scope. + // We will move up the runtime stack until we either + // find a defined function or run out of parent scopes. + EnvRef EnvRefs::findMixIdx(const EnvKey& name) const + { + for (const EnvRefs* current = this; current; current = current->pscope) + { + if (!name.isPrivate()) { + for (auto fwds : current->forwards) { + auto fwd = fwds->mixIdxs.find(name); + if (fwd != fwds->mixIdxs.end()) { + return { fwds, fwd->second }; + } + if (Module* mod = fwds->module) { + auto fwd = mod->mergedFwdMix.find(name); + if (fwd != mod->mergedFwdMix.end()) { + return { fwd->second }; + } + } + } + } + if (current->isImport) continue; + auto it = current->mixIdxs.find(name); + if (it != current->mixIdxs.end()) { + return { current, it->second }; + } + } + return nullidx; + } + // EO findFunction + + + // Get a function associated with the under [name]. + // Will lookup from the last runtime stack scope. + // We will move up the runtime stack until we either + // find a defined function or run out of parent scopes. + EnvRef EnvRefs::findFnIdx(const EnvKey& name) const + { + for (const EnvRefs* current = this; current; current = current->pscope) + { + if (!name.isPrivate()) { + for (auto fwds : current->forwards) { + auto fwd = fwds->fnIdxs.find(name); + if (fwd != fwds->fnIdxs.end()) { + return { fwds, fwd->second }; + } + if (Module* mod = fwds->module) { + auto fwd = mod->mergedFwdFn.find(name); + if (fwd != mod->mergedFwdFn.end()) { + return { fwd->second }; + } + } + } + } + if (current->isImport) continue; + auto it = current->fnIdxs.find(name); + if (it != current->fnIdxs.end()) { + return { current, it->second }; + } + } + return nullidx; + } + // EO findFunction + + // Get a value associated with the variable under [name]. + // If [global] flag is given, the lookup will be in the root. + // Otherwise lookup will be from the last runtime stack scope. + // We will move up the runtime stack until we either find a + // defined variable with a value or run out of parent scopes. + EnvRef EnvRefs::findVarIdx(const EnvKey& name) const + { + for (const EnvRefs* current = this; current; current = current->pscope) + { + for (auto fwds : current->forwards) { + auto fwd = fwds->varIdxs.find(name); + if (fwd != fwds->varIdxs.end()) { + if (name.isPrivate()) { + throw Exception::ParserException(root.compiler, + "Private members can't be accessed " + "from outside their modules."); + } + return { fwds, fwd->second }; + } + if (Module* mod = fwds->module) { + auto fwd = mod->mergedFwdVar.find(name); + if (fwd != mod->mergedFwdVar.end()) { + if (name.isPrivate()) { + throw Exception::ParserException(root.compiler, + "Private members can't be accessed " + "from outside their modules."); + } + return { fwd->second }; + } + } + } + if (current->isImport) continue; + auto it = current->varIdxs.find(name); + if (it != current->varIdxs.end()) { + return { current, it->second }; + } + } + return nullidx; + } + // EO findVarIdx + + // Get a value associated with the variable under [name]. + // If [global] flag is given, the lookup will be in the root. + // Otherwise lookup will be from the last runtime stack scope. + // We will move up the runtime stack until we either find a + // defined variable with a value or run out of parent scopes. + void EnvRefs::findVarIdxs(sass::vector& vidxs, const EnvKey& name) const + { + for (const EnvRefs* current = this; current; current = current->pscope) + { + if (current->isImport == false) { + auto it = current->varIdxs.find(name); + if (it != current->varIdxs.end()) { + vidxs.emplace_back(EnvRef{ + current, it->second }); + } + } + if (name.isPrivate()) continue; + for (auto fwds : current->forwards) { + auto fwd = fwds->varIdxs.find(name); + if (fwd != fwds->varIdxs.end()) { + vidxs.emplace_back(EnvRef{ + fwds, fwd->second }); + } + if (Module* mod = fwds->module) { + auto fwd = mod->mergedFwdVar.find(name); + if (fwd != mod->mergedFwdVar.end()) { + vidxs.emplace_back(EnvRef{ + fwd->second }); + } + } + } + } + } + // EO getVariable + + EnvRef EnvRefs::setModVar(const EnvKey& name, Value* value, bool guarded, const SourceSpan& pstate) const + { + auto it = varIdxs.find(name); + if (it != varIdxs.end()) { + root.setModVar(it->second, value, guarded, pstate); + return { it->second }; + } + for (auto fwds : forwards) { + auto it = fwds->varIdxs.find(name); + if (it != fwds->varIdxs.end()) { + root.setModVar(it->second, value, guarded, pstate); + return { it->second }; + } + } + return nullidx; + } + + + bool EnvRefs::hasNameSpace(const sass::string& ns) const + { + for (const EnvRefs* current = this; current; current = current->pscope) + { + if (current->isImport) continue; + Module* mod = current->module; + if (mod == nullptr) continue; + // Check if the namespace was registered + auto it = mod->moduse.find(ns); + if (it == mod->moduse.end()) continue; + // auto fwd = it->second.first->varIdxs.find(name); + // if (fwd != it->second.first->varIdxs.end()) { + // ValueObj& slot(root.getVariable({ 0xFFFFFFFF, fwd->second })); + // return slot != nullptr; + // } + if (it->second.second) { + return it->second.second->isCompiled; + } + else { + return true; + } + } + return false; + } + + EnvRef EnvRefs::findVarIdx(const EnvKey& name, const sass::string& ns) const + { + for (const EnvRefs* current = this; current; current = current->pscope) + { + if (current->isImport) continue; + Module* mod = current->module; + if (mod == nullptr) continue; + auto it = mod->moduse.find(ns); + if (it == mod->moduse.end()) continue; + if (EnvRefs* idxs = it->second.first) { + auto it = idxs->varIdxs.find(name); + if (it != idxs->varIdxs.end()) { + return { idxs, it->second }; + } + } + if (Module* mod = it->second.second) { + auto fwd = mod->mergedFwdVar.find(name); + if (fwd != mod->mergedFwdVar.end()) { + EnvRef vidx{ fwd->second }; + ValueObj& val = root.getVariable(vidx); + if (val != nullptr) return vidx; + } + } + } + return nullidx; + } + + + EnvRef EnvRefs::findMixIdx(const EnvKey& name, const sass::string& ns) const + { + for (const EnvRefs* current = this; current; current = current->pscope) + { + if (current->isImport) continue; + Module* mod = current->module; + if (mod == nullptr) continue; + auto it = mod->moduse.find(ns); + if (it == mod->moduse.end()) continue; + if (EnvRefs* idxs = it->second.first) { + auto it = idxs->mixIdxs.find(name); + if (it != idxs->mixIdxs.end()) { + return { idxs, it->second }; + } + } + if (Module* mod = it->second.second) { + auto fwd = mod->mergedFwdMix.find(name); + if (fwd != mod->mergedFwdMix.end()) { + return { fwd->second }; + } + } + } + return nullidx; + } + + EnvRef EnvRefs::findFnIdx(const EnvKey& name, const sass::string& ns) const + { + for (const EnvRefs* current = this; current; current = current->pscope) + { + if (current->isImport) continue; + Module* mod = current->module; + if (mod == nullptr) continue; + auto it = mod->moduse.find(ns); + if (it == mod->moduse.end()) continue; + if (EnvRefs* idxs = it->second.first) { + auto it = idxs->fnIdxs.find(name); + if (it != idxs->fnIdxs.end()) { + return { idxs, it->second }; + } + } + if (Module* mod = it->second.second) { + auto fwd = mod->mergedFwdFn.find(name); + if (fwd != mod->mergedFwdFn.end()) { + return { fwd->second }; + } + } + } + return nullidx; + } + + EnvRef EnvRoot::findVarIdx(const EnvKey& name, const sass::string& ns, bool global) const + { + if (stack.empty()) return nullidx; + auto& frame = global ? stack.front() : stack.back(); + if (ns.empty()) return frame->findVarIdx(name); + else return frame->findVarIdx(name, ns); + } + + // Find a function reference for [name] within the current scope stack. + // If [ns] is not empty, we will only look within loaded modules. + EnvRef EnvRoot::findFnIdx(const EnvKey& name, const sass::string& ns) const + { + if (stack.empty()) return nullidx; + if (ns.empty()) return stack.back()->findFnIdx(name); + else return stack.back()->findFnIdx(name, ns); + } + + // Find a function reference for [name] within the current scope stack. + // If [ns] is not empty, we will only look within loaded modules. + EnvRef EnvRoot::findMixIdx(const EnvKey& name, const sass::string& ns) const + { + if (stack.empty()) return nullidx; + if (ns.empty()) return stack.back()->findMixIdx(name); + else return stack.back()->findMixIdx(name, ns); + } + + void EnvRoot::findVarIdxs(sass::vector& vidxs, const EnvKey& name) const + { + if (stack.empty()) return; + stack.back()->findVarIdxs(vidxs, name); + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + EnvRef EnvRefs::setModVar(const EnvKey& name, const sass::string& ns, Value* value, bool guarded, const SourceSpan& pstate) + { + for (const EnvRefs* current = this; current; current = current->pscope) + { + if (current->isImport) continue; + Module* mod = current->module; + if (mod == nullptr) continue; + // Check if the namespace was registered + auto it = mod->moduse.find(ns); + if (it == mod->moduse.end()) continue; + // We set forwarded vars first! + if (Module* mod = it->second.second) { + auto fwd = mod->mergedFwdVar.find(name); + if (fwd != mod->mergedFwdVar.end()) { + root.setModVar(fwd->second, value, guarded, pstate); + return { fwd->second }; + } + } + if (EnvRefs* idxs = it->second.first) { + EnvRef vidx = idxs->setModVar(name, value, guarded, pstate); + if (vidx.isValid()) return vidx; + } + } + return nullidx; + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // Imports are transparent for variables, functions and mixins + // We always need to create entities inside the parent scope + bool EnvRef::isImport() const { + if (idxs == nullptr) return false; + return idxs->isImport; + } + + // Flag if this scope is considered internal + bool EnvRef::isInternal() const { + if (idxs == nullptr) return false; + return idxs->isInternal; + } + + // Rules like `@if`, `@for` etc. are semi-global (permeable). + // Assignments directly in those can bleed to the root scope. + bool EnvRef::isSemiGlobal() const { + if (idxs == nullptr) return false; + return idxs->isSemiGlobal; + } + + // Set to true once we are compiled via use or forward + // An import does load the sheet, but does not compile it + // Compiling it means hard-baking the config vars into it + bool EnvRef::isCompiled() const { + if (idxs == nullptr) return false; + return idxs->isCompiled; + } + + // Very small helper for debugging + sass::string EnvRef::toString() const + { + sass::sstream strm; + strm << offset; + return strm.str(); + } + // EO toString + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + +} diff --git a/src/environment_stack.hpp b/src/environment_stack.hpp new file mode 100644 index 0000000000..f284dbac3a --- /dev/null +++ b/src/environment_stack.hpp @@ -0,0 +1,569 @@ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#ifndef SASS_ENV_STACK_HPP +#define SASS_ENV_STACK_HPP + +// sass.hpp must go before all system headers +// to get the __EXTENSIONS__ fix on Solaris. +#include "capi_sass.hpp" + +#include "ast_fwd_decl.hpp" +#include "environment_cnt.hpp" + +namespace Sass { + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // Helper typedef for our frame stack type + typedef sass::vector EnvFrameVector; + + // Constant similar to nullptr + extern const EnvRef nullidx; + + ///////////////////////////////////////////////////////////////////////// + // Base class/struct for environment references. These can be variables, + // functions or mixins. Each one belongs to an environment frame/scope, + // as determined and seen during parsing. Similar to how C organizes + // local variables via function stack pointers. This allows us to do + // improvements during parse or eval phase, e.g. if a variable is first + // seen, a new slot is assigned for it on the current frame/scope. When + // we later see this variable being used, we know the static offset of + // that variable on the frame. E.g. if a scope has two variables, one + // will be at offset 0, the other at offset 1. When we later see one of + // these variables being used, we know the offset to this variable to be + // used to access it on the current active stack frame. This removes the + // need for a dynamic lookup during runtime to improve performance. + ///////////////////////////////////////////////////////////////////////// + class EnvRef + { + public: + + // The lexical frame pointer + // Each parsed scope gets its own + const EnvRefs* idxs = nullptr; + + // Local offset within the frame + uint32_t offset = 0xFFFFFFFF; + + // Default constructor + EnvRef() {} + + // Value constructor + EnvRef( + uint32_t offset) : + offset(offset) + {} + + // Value constructor + EnvRef( + const EnvRefs* idxs, + uint32_t offset) : + idxs(idxs), + offset(offset) + {} + + // Implement native equality operator + inline bool operator==(const EnvRef& rhs) const { + return idxs == rhs.idxs + && offset == rhs.offset; + } + + // Implement native inequality operator + inline bool operator!=(const EnvRef& rhs) const { + return idxs != rhs.idxs + || offset != rhs.offset; + } + + // Implement operator to allow use in sets + inline bool operator<(const EnvRef& rhs) const { + if (idxs < rhs.idxs) return true; + return offset < rhs.offset; + } + + // Check if reference is valid + inline bool isValid() const { // 3% + return offset != 0xFFFFFFFF; + } + + // Check if entity is read-only + inline bool isPrivate(uint32_t privateOffset) { + return idxs == nullptr && + offset <= privateOffset; + } + + // Imports are transparent for variables, functions and mixins + // We always need to create entities inside the parent scope + bool isImport() const; + + // Flag if this scope is considered internal + bool isInternal() const; + + // Rules like `@if`, `@for` etc. are semi-global (permeable). + // Assignments directly in those can bleed to the root scope. + bool isSemiGlobal() const; + + // Set to true once we are compiled via use or forward + // An import does load the sheet, but does not compile it + // Compiling it means hard-baking the config vars into it + bool isCompiled() const; + + // Small helper for debugging + sass::string toString() const; + }; + + ///////////////////////////////////////////////////////////////////////// + // + ///////////////////////////////////////////////////////////////////////// + + // Runtime query structure + // Created for every EnvFrame + // Survives the actual EnvFrame + class EnvRefs { + public: + + // EnvRoot reference + EnvRoot& root; + + // Parent is needed during runtime for + // dynamic setter and getter by EnvKey. + EnvRefs* pscope; + + // Lexical scope entries + VidxEnvKeyMap varIdxs; + MidxEnvKeyMap mixIdxs; + FidxEnvKeyMap fnIdxs; + + size_t varOffset = NPOS; + size_t mixOffset = NPOS; + size_t fnOffset = NPOS; + + // Any import may add forwarded entities to current scope + // Since those scopes are dynamic and not global, we can't + // simply insert our references. Therefore we must have the + // possibility to hoist forwarded entities at any lexical scope. + // All @use as "*" do not get exposed to the parent scope though. + sass::vector forwards; + + // Some scopes are connected to a module + // Those expose some additional exports + // Modules are global so we just link them + Module* module = nullptr; + + // Imports are transparent for variables, functions and mixins + // We always need to create entities inside the parent scope + bool isImport = false; + + // Flag if this scope is considered internal + bool isInternal = false; + + // Rules like `@if`, `@for` etc. are semi-global (permeable). + // Assignments directly in those can bleed to the root scope. + bool isSemiGlobal = false; + + // Set to true once we are compiled via use or forward + // An import does load the sheet, but does not compile it + // Compiling it means hard-baking the config vars into it + bool isCompiled = false; + + // Value constructor + EnvRefs(EnvRoot& root, + EnvRefs* pscope, + bool isImport, + bool isInternal, + bool isSemiGlobal) : + root(root), + pscope(pscope), + isImport(isImport), + isInternal(isInternal), + isSemiGlobal(isSemiGlobal) + {} + + ///////////////////////////////////////////////////////////////////////// + // Register an occurrence during parsing, reserving the offset. + // Only structures are create when calling this, the real work + // is done on runtime, where actual stack objects are queried. + ///////////////////////////////////////////////////////////////////////// + + // Register new variable on local stack + // Invoked mostly by stylesheet parser + EnvRef createVariable(const EnvKey& name); + + // Register new function on local stack + // Mostly invoked by built-in functions + // Then invoked for custom C-API function + // Finally for every parsed function rule + EnvRef createFunction(const EnvKey& name, bool special = false); + + // Register new mixin on local stack + // Only invoked for mixin rules + // But also for content blocks + EnvRef createMixin(const EnvKey& name); + + void findVarIdxs(sass::vector& vidxs, const EnvKey& name) const; + + // Get a mixin associated with the under [name]. + // Will lookup from the last runtime stack scope. + // We will move up the runtime stack until we either + // find a defined mixin or run out of parent scopes. + + // Get a function associated with the under [name]. + // Will lookup from the last runtime stack scope. + // We will move up the runtime stack until we either + // find a defined function or run out of parent scopes. + + + // Get a value associated with the variable under [name]. + // If [global] flag is given, the lookup will be in the root. + // Otherwise lookup will be from the last runtime stack scope. + // We will move up the runtime stack until we either find a + // defined variable with a value or run out of parent scopes. + + + // Get reference of entity with [name] under namespace [ns]. + // Namespaced entities can only be exported by actual modules. + // Will process all parent scopes, skipping any imports as they are not + // "real" modules, until a module is found that exports into given [ns]. + // Continues until a corresponding variable with [name] is found under [ns]. + EnvRef findVarIdx(const EnvKey& name, const sass::string& ns) const; + EnvRef findFnIdx(const EnvKey& name, const sass::string& ns) const; + EnvRef findMixIdx(const EnvKey& name, const sass::string& ns) const; + + // Get reference of entity with [name] without any namespace. + // Non-namespaced entities are either directly declared in the root + // stylesheet or via forwarded module entities into star "*" namespace. + EnvRef findVarIdx(const EnvKey& name) const; + EnvRef findFnIdx(const EnvKey& name) const; + EnvRef findMixIdx(const EnvKey& name) const; + + bool hasNameSpace(const sass::string& ns) const; + + // Find function only in local frame + + + EnvRef setModVar(const EnvKey& name, Value* value, bool guarded, const SourceSpan& pstate) const; + + + EnvRef setModVar(const EnvKey& name, const sass::string& ns, Value* value, bool guarded, const SourceSpan& pstate); + + }; + + ///////////////////////////////////////////////////////////////////////// + // EnvFrames are created during the parsing phase. + ///////////////////////////////////////////////////////////////////////// + + class EnvFrame { + + public: + + // Reference to stack + // We manage it ourself + EnvFrameVector& stack; + + // Our runtime object + EnvRefs* idxs; + + private: + + public: + + // Value constructor + EnvFrame( + Compiler& compiler, + bool isSemiGlobal, + bool isModule = false, + bool isImport = false); + + // Destructor + ~EnvFrame(); + + }; + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + class EnvRoot { + + public: + + Compiler& compiler; + + // Reference to stack + // We manage it ourself + EnvFrameVector& stack; + + // Root runtime env + EnvRefs* idxs; + + private: + + // We work together + friend class Compiler; + friend class EnvScope; + friend class EnvFrame; + friend class EnvRefs; + + // Growable runtime stack (get offset by xxxStackPtr). + // These vectors are the main stacks during runtime. + // When a scope with two variables is executed, two + // new items are added to the variables stack. If the + // same scope is called more than once, its variables + // are added multiple times so we can revert to them. + // variables[varStackPtr[vidx.frame] + vidx.offset] + sass::vector varStack; + sass::vector mixStack; + sass::vector fnStack; + public: // Optimize again + // Internal functions are stored here + sass::vector intFunction; + private: + sass::vector intMixin; + sass::vector intVariables; + + // Last private accessible item + uint32_t privateVarOffset = 0; + uint32_t privateMixOffset = 0; + uint32_t privateFnOffset = 0; + + // All created runtime variable objects. + // Needed to track the memory allocations + // And useful to resolve parents indirectly + // Access it by absolute `frameOffset` + sass::vector scopes; + + public: + + // Value constructor + EnvRoot(Compiler& compiler); + + // Destructor + ~EnvRoot() { + // Pop from stack + stack.pop_back(); + // Take care of scope pointers + for (EnvRefs* idx : scopes) { + delete idx; + } + // Delete our env + delete idxs; + } + + // Runtime check to see if we are currently in global scope + bool isGlobal() const { return idxs->root.stack.size() == 1; } + + // Get value instance by stack index reference + // Just converting and returning reference to array offset + ValueObj& getVariable(const EnvRef& vidx); + ValueObj& getModVar(const uint32_t offset); + + // Get function instance by stack index reference + // Just converting and returning reference to array offset + CallableObj& getFunction(const EnvRef& vidx); + CallableObj& getModFn(const uint32_t offset); + + // Get mixin instance by stack index reference + // Just converting and returning reference to array offset + CallableObj& getMixin(const EnvRef& midx); + CallableObj& getModMix(const uint32_t offset); + + // Set items on runtime/evaluation phase via references + // Just converting reference to array offset and assigning + void setVariable(const EnvRef& vidx, Value* value, bool guarded); + + + void setModVar(const uint32_t offset, Value* value, bool guarded, const SourceSpan& pstate); + + // Set items on runtime/evaluation phase via references + // Just converting reference to array offset and assigning + void setFunction(const EnvRef& fidx, UserDefinedCallable* value, bool guarded); + + // Set items on runtime/evaluation phase via references + // Just converting reference to array offset and assigning + void setMixin(const EnvRef& midx, UserDefinedCallable* value, bool guarded); + + // Get a value associated with the variable under [name]. + // If [global] flag is given, the lookup will be in the root. + // Otherwise lookup will be from the last runtime stack scope. + // We will move up the runtime stack until we either find a + // defined variable with a value or run out of parent scopes. + + EnvRef findFnIdx( + const EnvKey& name, + const sass::string& ns) const; + + EnvRef findMixIdx( + const EnvKey& name, + const sass::string& ns) const; + + EnvRef findVarIdx( + const EnvKey& name, + const sass::string& ns, + bool global = false) const; + + void findVarIdxs( + sass::vector& vidxs, + const EnvKey& name) const; + + }; + + ///////////////////////////////////////////////////////////////////////// + // EnvScopes are created during evaluation phase. When we enter a parsed + // scope, e.g. a function, mixin or style-rule, we create a new EnvScope + // object on the stack and pass it the runtime environment and the current + // stack frame (in form of a EnvRefs pointer). We will "allocate" the needed + // space for scope items and update any offset pointers. Once we go out of + // scope the previous state is restored by unwinding the runtime stack. + ///////////////////////////////////////////////////////////////////////// + + class EnvScope { + + private: + + // Runtime environment + EnvRoot& env; + + // Frame stack index references + EnvRefs* idxs; + + // Remember previous "addresses" + // Restored when we go out of scope + size_t oldVarOffset; + size_t oldMixOffset; + size_t oldFnOffset; + + public: + + // Put frame onto stack + EnvScope( + EnvRoot& env, + EnvRefs* idxs) : + env(env), + idxs(idxs), + oldVarOffset(0), + oldMixOffset(0), + oldFnOffset(0) + { + + // The frame might be fully empty + // Meaning it no scoped items at all + if (idxs == nullptr) return; + + if (!idxs->isInternal) { + + // Check if we have scoped variables + if (idxs->varIdxs.size() != 0) { + // Get offset into variable vector + size_t oldVarSize = env.varStack.size(); + // Remember previous frame "addresses" + oldVarOffset = idxs->varOffset; + // Update current frame offset address + idxs->varOffset = oldVarSize; + // Create space for variables in this frame scope + env.varStack.resize(oldVarSize + idxs->varIdxs.size()); + } + + // Check if we have scoped mixins + if (idxs->mixIdxs.size() != 0) { + // Get offset into mixin vector + size_t oldMixSize = env.mixStack.size(); + // Remember previous frame "addresses" + oldMixOffset = idxs->mixOffset; + // Update current frame offset address + idxs->mixOffset = oldMixSize; + // Create space for mixins in this frame scope + env.mixStack.resize(oldMixSize + idxs->mixIdxs.size()); + } + + // Check if we have scoped functions + if (idxs->fnIdxs.size() != 0) { + // Get offset into function vector + size_t oldFnSize = env.fnStack.size(); + // Remember previous frame "addresses" + oldFnOffset = idxs->fnOffset; + // Update current frame offset address + idxs->fnOffset = oldFnSize; + // Create space for functions in this frame scope + env.fnStack.resize(oldFnSize + idxs->fnIdxs.size()); + } + + } + + + // Push frame onto stack + // Mostly for dynamic lookups + env.stack.push_back(idxs); + + } + // EO ctor + + // Restore old state on destruction + ~EnvScope() + { + + // The frame might be fully empty + // Meaning it no scoped items at all + if (idxs == nullptr) return; + + if (!idxs->isInternal) { + + // Check if we had scoped variables + if (idxs->varIdxs.size() != 0) { + // Truncate variable vector + env.varStack.resize( + env.varStack.size() + - idxs->varIdxs.size()); + // Restore old frame address + idxs->varOffset = oldVarOffset; + } + + // Check if we had scoped mixins + if (idxs->mixIdxs.size() != 0) { + // Truncate existing vector + env.mixStack.resize( + env.mixStack.size() + - idxs->mixIdxs.size()); + // Restore old frame address + idxs->mixOffset = oldMixOffset; + } + + // Check if we had scoped functions + if (idxs->fnIdxs.size() != 0) { + // Truncate existing vector + env.fnStack.resize( + env.fnStack.size() + - idxs->fnIdxs.size()); + // Restore old frame address + idxs->fnOffset = oldFnOffset; + } + + } + + // Pop frame from stack + env.stack.pop_back(); + + } + // EO dtor + + }; + + ///////////////////////////////////////////////////////////////////////// + // Base class for any scope. We want to keep the pointer + // separate from the main object in this case here. They are + // mostly managed by EnvRoot and stay alive with main context. + ///////////////////////////////////////////////////////////////////////// + + class Env + { + public: + EnvRefs* idxs = nullptr; + Env(EnvRefs* idxs) + : idxs(idxs) {} + }; + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + +} + +#endif diff --git a/src/error_handling.cpp b/src/error_handling.cpp index 8fc840e610..e7e198ec17 100644 --- a/src/error_handling.cpp +++ b/src/error_handling.cpp @@ -1,89 +1,89 @@ -// sass.hpp must go before all system headers to get the -// __EXTENSIONS__ fix on Solaris. -#include "sass.hpp" - -#include "ast.hpp" -#include "prelexer.hpp" -#include "backtrace.hpp" #include "error_handling.hpp" -#include +#include "ast_selectors.hpp" +#include "exceptions.hpp" +#include "extension.hpp" namespace Sass { + StackTraces convertTraces(BackTraces traces) + { + // This will trigger StackTrace constructor + // Copies necessary stuff from BackTrace + return { traces.begin(), traces.end() }; + } + namespace Exception { - Base::Base(SourceSpan pstate, sass::string msg, Backtraces traces) + Base::Base(sass::string msg, BackTraces traces) : std::runtime_error(msg.c_str()), msg(msg), - prefix("Error"), pstate(pstate), traces(traces) - { } - - InvalidSass::InvalidSass(SourceSpan pstate, Backtraces traces, sass::string msg) - : Base(pstate, msg, traces) + traces(traces.begin(), traces.end()) { } - - InvalidParent::InvalidParent(Selector* parent, Backtraces traces, Selector* selector) - : Base(selector->pstate(), def_msg, traces), parent(parent), selector(selector) + Base::Base(sass::string msg, BackTraces traces, SourceSpan pstate) + : Base(msg, traces) { - msg = "Invalid parent selector for " - "\"" + selector->to_string(Sass_Inspect_Options()) + "\": " - "\"" + parent->to_string(Sass_Inspect_Options()) + "\""; + Base::traces.push_back(pstate); } - InvalidVarKwdType::InvalidVarKwdType(SourceSpan pstate, Backtraces traces, sass::string name, const Argument* arg) - : Base(pstate, def_msg, traces), name(name), arg(arg) - { - msg = "Variable keyword argument map must have string keys.\n" + - name + " is not a string in " + arg->to_string() + "."; - } + SassRuntimeException2::SassRuntimeException2( + sass::string msg, BackTraces traces) + : Base(msg, traces) {} - InvalidArgumentType::InvalidArgumentType(SourceSpan pstate, Backtraces traces, sass::string fn, sass::string arg, sass::string type, const Value* value) - : Base(pstate, def_msg, traces), fn(fn), arg(arg), type(type), value(value) - { - msg = arg + ": \""; - if (value) msg += value->to_string(Sass_Inspect_Options()); - msg += "\" is not a " + type + " for `" + fn + "'"; - } + SassRuntimeException2::SassRuntimeException2( + sass::string msg, BackTraces traces, SourceSpan pstate) + : Base(msg, traces, pstate) {} - MissingArgument::MissingArgument(SourceSpan pstate, Backtraces traces, sass::string fn, sass::string arg, sass::string fntype) - : Base(pstate, def_msg, traces), fn(fn), arg(arg), fntype(fntype) + InvalidParent::InvalidParent(Selector* parent, BackTraces traces, Selector* selector) + : Base(def_msg, traces, selector->pstate()), parent(parent), selector(selector) { - msg = fntype + " " + fn + " is missing argument " + arg + "."; + msg = "Parent " + "\"" + parent->inspect() + "\"" + " is incompatible with this selector."; } - InvalidSyntax::InvalidSyntax(SourceSpan pstate, Backtraces traces, sass::string msg) - : Base(pstate, msg, traces) + InvalidSyntax::InvalidSyntax(BackTraces traces, sass::string msg) + : Base(msg, traces) { } - NestingLimitError::NestingLimitError(SourceSpan pstate, Backtraces traces, sass::string msg) - : Base(pstate, msg, traces) + CustomImportError::CustomImportError(BackTraces traces, sass::string msg) + : Base(msg, traces) { } - DuplicateKeyError::DuplicateKeyError(Backtraces traces, const Map& dup, const Expression& org) - : Base(org.pstate(), def_msg, traces), dup(dup), org(org) + CustomImportNotFound::CustomImportNotFound(BackTraces traces, sass::string file) + : Base(def_msg, traces) { - msg = "Duplicate key " + dup.get_duplicate_key()->inspect() + " in map (" + org.inspect() + ")."; + msg = "Can't find stylesheet \"" + file + "\"."; + msg += "\nAs requested by custom importer."; } - TypeMismatch::TypeMismatch(Backtraces traces, const Expression& var, const sass::string type) - : Base(var.pstate(), def_msg, traces), var(var), type(type) + CustomImportAmbigous::CustomImportAmbigous(BackTraces traces, sass::string file) + : Base(def_msg, traces) { - msg = var.to_string() + " is not an " + type + "."; + msg = "CustomImportAmbigous \"" + file + "\"."; + msg += "\nAs requested by custom importer."; } - InvalidValue::InvalidValue(Backtraces traces, const Expression& val) - : Base(val.pstate(), def_msg, traces), val(val) + CustomImportLoadError::CustomImportLoadError(BackTraces traces, sass::string file) + : Base(def_msg, traces) { - msg = val.to_string() + " isn't a valid CSS value."; + msg = "CustomImportLoadError \"" + file + "\"."; + msg += "\nAs requested by custom importer."; } + + + RecursionLimitError::RecursionLimitError() + : Base(msg_recursion_limit, {}) {} - StackError::StackError(Backtraces traces, const AST_Node& node) - : Base(node.pstate(), def_msg, traces), node(node) + DuplicateKeyError::DuplicateKeyError(BackTraces traces, const Map& dup, const Value& org) + : Base(def_msg, traces), dup(dup), org(org) { - msg = "stack level too deep"; + // msg = "Duplicate key " + dup.get_duplicate_key()->inspect() + " in map (" + org.inspect() + ")."; + msg = "Duplicate key."; // dart-sass keeps it simple ... } + EndlessExtendError::EndlessExtendError(Backtraces traces, const AST_Node& node) + : Base(node.pstate(), def_msg, traces), node(node) EndlessExtendError::EndlessExtendError(Backtraces traces, const AST_Node& node) : Base(node.pstate(), def_msg, traces), node(node) { @@ -92,148 +92,222 @@ namespace Sass { IncompatibleUnits::IncompatibleUnits(const Units& lhs, const Units& rhs) { - msg = "Incompatible units: '" + rhs.unit() + "' and '" + lhs.unit() + "'."; + sass::string msg("No "); + msg += pluralize(Strings::argument, names.size()); + msg += " named "; + msg += toSentence(names, Strings::_or_); + msg += "."; + return msg; } - IncompatibleUnits::IncompatibleUnits(const UnitType lhs, const UnitType rhs) - { - msg = sass::string("Incompatible units: '") + unit_to_string(rhs) + "' and '" + unit_to_string(lhs) + "'."; + sass::string formatTooManyArguments(size_t given, size_t expected) { + sass::ostream msg; + msg << "Only " << expected << " "; + msg << pluralize("argument", expected); + msg << " allowed, but " << given << " "; + msg << pluralize("was", given, "were"); + msg << " passed."; + return msg.str(); } - AlphaChannelsNotEqual::AlphaChannelsNotEqual(const Expression* lhs, const Expression* rhs, enum Sass_OP op) - : OperationError(), lhs(lhs), rhs(rhs), op(op) - { - msg = "Alpha channels must be equal: " + - lhs->to_string({ NESTED, 5 }) + - " " + sass_op_to_name(op) + " " + - rhs->to_string({ NESTED, 5 }) + "."; + sass::string formatTooManyArguments(const EnvKeyFlatMap& given, const Sass::EnvKeySet& expected) { + Sass::EnvKeySet superfluous; + for (auto pair : given) { + if (expected.count(pair.first) == 0) { + superfluous.insert(pair.first); + } + } + return "No argument named " + + toSentence(superfluous, "or") + "."; } - ZeroDivisionError::ZeroDivisionError(const Expression& lhs, const Expression& rhs) - : OperationError(), lhs(lhs), rhs(rhs) - { - msg = "divided by 0"; + sass::string formatTooManyArguments(const EnvKeyFlatMap& given, const Sass::EnvKeySet& expected) { + Sass::EnvKeySet superfluous; + for (auto pair : given) { + if (expected.count(pair.first) == 0) { + superfluous.insert(pair.first); + } + } + return "No argument named " + + toSentence(superfluous, "or") + "."; } - UndefinedOperation::UndefinedOperation(const Expression* lhs, const Expression* rhs, enum Sass_OP op) - : OperationError(), lhs(lhs), rhs(rhs), op(op) - { - msg = def_op_msg + ": \"" + - lhs->to_string({ NESTED, 5 }) + - " " + sass_op_to_name(op) + " " + - rhs->to_string({ TO_SASS, 5 }) + - "\"."; + sass::string formatTooManyArguments(const EnvKeyFlatMap& superfluous) { + return "No argument named " + + toSentence(superfluous, "or") + "."; } - InvalidNullOperation::InvalidNullOperation(const Expression* lhs, const Expression* rhs, enum Sass_OP op) - : UndefinedOperation(lhs, rhs, op) + TooManyArguments::TooManyArguments(BackTraces traces, size_t given, size_t expected) + : Base(formatTooManyArguments(given, expected), traces) + {} + + TooManyArguments::TooManyArguments(BackTraces traces, const EnvKeyFlatMap& given, const Sass::EnvKeySet& expected) + : Base(formatTooManyArguments(given, expected), traces) + {} + + TooManyArguments::TooManyArguments(BackTraces traces, const EnvKeyFlatMap& superflous) + : Base(formatTooManyArguments(superflous), traces) + {} + + MissingArgument::MissingArgument(BackTraces traces, const EnvKey& name) + : Base("Missing argument " + name.norm() + ".", traces) + {} + + ArgumentGivenTwice::ArgumentGivenTwice(BackTraces traces, const EnvKey& name) + : Base("Argument " + name.norm() + " name was passed both by position and by name.", traces) + {} + + UnknownNamedArgument::UnknownNamedArgument(SourceSpan pstate, BackTraces traces, EnvKeyFlatMap names) + : Base(formatUnknownNamedArgument(names), traces, pstate) { - msg = def_op_null_msg + ": \"" + lhs->inspect() + " " + sass_op_to_name(op) + " " + rhs->inspect() + "\"."; } - SassValueError::SassValueError(Backtraces traces, SourceSpan pstate, OperationError& err) - : Base(pstate, err.what(), traces) + UnknownNamedArgument2::UnknownNamedArgument2(BackTraces traces, EnvKeyFlatMap names) + : Base(formatUnknownNamedArgument(names), traces) { - msg = err.what(); - prefix = err.errtype(); } - TopLevelParent::TopLevelParent(Backtraces traces, SourceSpan pstate) - : Base(pstate, "Top-level selectors may not contain the parent selector \"&\".", traces) + sass::string formatUnknownNamedArgument2(const EnvKeyFlatMap& names) { - + sass::string msg("No "); + msg += pluralize(Strings::argument, names.size()); + msg += " named "; + msg += toSentence(names, Strings::_or_); + msg += "."; + return msg; } - UnsatisfiedExtend::UnsatisfiedExtend(Backtraces traces, Extension extension) - : Base(extension.target->pstate(), "The target selector was not found.\n" - "Use \"@extend " + extension.target->to_string() + " !optional\" to avoid this error.", traces) - { + InvalidCssValue::InvalidCssValue(BackTraces traces, const Value& val) + : Base(val.inspect() + " isn't a valid CSS value.", traces, val.pstate()) + {} + + InvalidCssValue2::InvalidCssValue2(BackTraces traces, const Value& val) + : Base(val.inspect() + " isn't a valid CSS value.", traces, val.pstate()) + {} + + // Thrown when a parent selector is used without any parent + TopLevelParent::TopLevelParent(BackTraces traces, SourceSpan pstate) + : Base("Top-level selectors may not contain the parent selector \"&\".", traces, pstate) + {} + + // Thrown when a non-optional extend found nothing to extend + UnsatisfiedExtend::UnsatisfiedExtend(BackTraces traces, Extension extension) + : Base("The target selector was not found.\n" + // Calling inspect to the placeholder is visible + "Use \"@extend " + extension.target->inspect() + + " !optional\" to avoid this error.", + traces, extension.target->pstate()) + {} + + // Thrown when we extend across incompatible media contexts + ExtendAcrossMedia::ExtendAcrossMedia(BackTraces traces, Extension extension) + : Base("You may not @extend selectors across media queries.", traces) + {} + + // Thrown when we find an unexpected UTF8 sequence + InvalidUnicode::InvalidUnicode(SourceSpan pstate, BackTraces traces) + : Base("Invalid UTF-8.", traces, pstate) + {} + + SassScriptException::SassScriptException(sass::string msg, + BackTraces traces, SourceSpan pstate, sass::string name) : + Base(name.empty() ? msg : "$" + name + ": " + msg, traces) + {} + + - } - ExtendAcrossMedia::ExtendAcrossMedia(Backtraces traces, Extension extension) - : Base(extension.target->pstate(), "You may not @extend selectors across media queries.\n" - "Use \"@extend " + extension.target->to_string() + " !optional\" to avoid this error.", traces) - { - } - - } - void warn(sass::string msg, SourceSpan pstate) - { - std::cerr << "Warning: " << msg << std::endl; - } - void warning(sass::string msg, SourceSpan pstate) - { - sass::string cwd(Sass::File::get_cwd()); - sass::string abs_path(Sass::File::rel2abs(pstate.getPath(), cwd, cwd)); - sass::string rel_path(Sass::File::abs2rel(pstate.getPath(), cwd, cwd)); - sass::string output_path(Sass::File::path_for_console(rel_path, abs_path, pstate.getPath())); - std::cerr << "WARNING on line " << pstate.getLine() << ", column " << pstate.getColumn() << " of " << output_path << ":" << std::endl; - std::cerr << msg << std::endl << std::endl; - } - void warn(sass::string msg, SourceSpan pstate, Backtrace* bt) - { - warn(msg, pstate); - } - void deprecated_function(sass::string msg, SourceSpan pstate) - { - sass::string cwd(Sass::File::get_cwd()); - sass::string abs_path(Sass::File::rel2abs(pstate.getPath(), cwd, cwd)); - sass::string rel_path(Sass::File::abs2rel(pstate.getPath(), cwd, cwd)); - sass::string output_path(Sass::File::path_for_console(rel_path, abs_path, pstate.getPath())); - - std::cerr << "DEPRECATION WARNING: " << msg << std::endl; - std::cerr << "will be an error in future versions of Sass." << std::endl; - std::cerr << " on line " << pstate.getLine() << " of " << output_path << std::endl; - } + + + + + + + + + + + + ///////////////////////////////////////////////////////////////////////// + // Various value operation errors + ///////////////////////////////////////////////////////////////////////// + + ZeroDivisionError::ZeroDivisionError(const Value& lhs, const Value& rhs) + : OperationError("divided by 0") + {} + + IncompatibleUnits::IncompatibleUnits(const Units& lhs, const Units& rhs) + : OperationError("Incompatible units " + + rhs.unit() + " and " + + lhs.unit() + ".") + {} + + AlphaChannelsNotEqual::AlphaChannelsNotEqual(const ColorRgba* lhs, const ColorRgba* rhs, enum SassOperator op) + : OperationError("Alpha channels must be equal: " + + lhs->inspect() + " " + + sass_op_to_name(op) + " " + + rhs->inspect() + ".") + {} + + + InvalidNullOperation::InvalidNullOperation(const Value* lhs, const Value* rhs, enum SassOperator op) + : OperationError("Invalid null operation: \"" + + lhs->inspect() + " " + + sass_op_to_name(op) + " " + + rhs->inspect() + "\".") + {} + + UndefinedOperation::UndefinedOperation(const Value* lhs, const Value* rhs, enum SassOperator op) + : OperationError("Undefined operation: \"" + + lhs->inspect() + " " + + sass_op_separator(op) + " " + + rhs->inspect() + "\".") + {} + + + + + + + + + + + + + + + + + + +} void deprecated(sass::string msg, sass::string msg2, bool with_column, SourceSpan pstate) { - sass::string cwd(Sass::File::get_cwd()); - sass::string abs_path(Sass::File::rel2abs(pstate.getPath(), cwd, cwd)); - sass::string rel_path(Sass::File::abs2rel(pstate.getPath(), cwd, cwd)); - sass::string output_path(Sass::File::path_for_console(rel_path, pstate.getPath(), pstate.getPath())); - + sass::string output_path(pstate.getDebugPath()); std::cerr << "DEPRECATION WARNING on line " << pstate.getLine(); - // if (with_column) std::cerr << ", column " << pstate.column + pstate.offset.column + 1; + if (with_column) std::cerr << ", column " << pstate.getColumn(); if (output_path.length()) std::cerr << " of " << output_path; - std::cerr << ":" << std::endl; - std::cerr << msg << std::endl; - if (msg2.length()) std::cerr << msg2 << std::endl; - std::cerr << std::endl; + std::cerr << ':' << STRMLF; + std::cerr << msg << STRMLF; + if (msg2.length()) std::cerr << msg2 << STRMLF; + std::cerr << STRMLF; } - void deprecated_bind(sass::string msg, SourceSpan pstate) - { - sass::string cwd(Sass::File::get_cwd()); - sass::string abs_path(Sass::File::rel2abs(pstate.getPath(), cwd, cwd)); - sass::string rel_path(Sass::File::abs2rel(pstate.getPath(), cwd, cwd)); - sass::string output_path(Sass::File::path_for_console(rel_path, abs_path, pstate.getPath())); - - std::cerr << "WARNING: " << msg << std::endl; - std::cerr << " on line " << pstate.getLine() << " of " << output_path << std::endl; - std::cerr << "This will be an error in future versions of Sass." << std::endl; - } - - // should be replaced with error with backtraces - void coreError(sass::string msg, SourceSpan pstate) - { - Backtraces traces; - throw Exception::InvalidSyntax(pstate, traces, msg); - } - void error(sass::string msg, SourceSpan pstate, Backtraces& traces) + void error(const sass::string& msg, SourceSpan pstate, BackTraces& traces) { - traces.push_back(Backtrace(pstate)); - throw Exception::InvalidSyntax(pstate, traces, msg); + traces.push_back(BackTrace(pstate)); + throw Exception::InvalidSyntax(traces, msg); } } diff --git a/src/error_handling.hpp b/src/error_handling.hpp deleted file mode 100644 index 8fd5fdabf1..0000000000 --- a/src/error_handling.hpp +++ /dev/null @@ -1,248 +0,0 @@ -#ifndef SASS_ERROR_HANDLING_H -#define SASS_ERROR_HANDLING_H - -// sass.hpp must go before all system headers to get the -// __EXTENSIONS__ fix on Solaris. -#include "sass.hpp" - -#include -#include -#include -#include "units.hpp" -#include "position.hpp" -#include "backtrace.hpp" -#include "ast_fwd_decl.hpp" -#include "sass/functions.h" - -namespace Sass { - - struct Backtrace; - - namespace Exception { - - const sass::string def_msg = "Invalid sass detected"; - const sass::string def_op_msg = "Undefined operation"; - const sass::string def_op_null_msg = "Invalid null operation"; - const sass::string def_nesting_limit = "Code too deeply nested"; - - class Base : public std::runtime_error { - protected: - sass::string msg; - sass::string prefix; - public: - SourceSpan pstate; - Backtraces traces; - public: - Base(SourceSpan pstate, sass::string msg, Backtraces traces); - virtual const char* errtype() const { return prefix.c_str(); } - virtual const char* what() const throw() { return msg.c_str(); } - virtual ~Base() throw() {}; - }; - - class InvalidSass : public Base { - public: - InvalidSass(SourceSpan pstate, Backtraces traces, sass::string msg); - virtual ~InvalidSass() throw() {}; - }; - - class InvalidParent : public Base { - protected: - Selector* parent; - Selector* selector; - public: - InvalidParent(Selector* parent, Backtraces traces, Selector* selector); - virtual ~InvalidParent() throw() {}; - }; - - class MissingArgument : public Base { - protected: - sass::string fn; - sass::string arg; - sass::string fntype; - public: - MissingArgument(SourceSpan pstate, Backtraces traces, sass::string fn, sass::string arg, sass::string fntype); - virtual ~MissingArgument() throw() {}; - }; - - class InvalidArgumentType : public Base { - protected: - sass::string fn; - sass::string arg; - sass::string type; - const Value* value; - public: - InvalidArgumentType(SourceSpan pstate, Backtraces traces, sass::string fn, sass::string arg, sass::string type, const Value* value = 0); - virtual ~InvalidArgumentType() throw() {}; - }; - - class InvalidVarKwdType : public Base { - protected: - sass::string name; - const Argument* arg; - public: - InvalidVarKwdType(SourceSpan pstate, Backtraces traces, sass::string name, const Argument* arg = 0); - virtual ~InvalidVarKwdType() throw() {}; - }; - - class InvalidSyntax : public Base { - public: - InvalidSyntax(SourceSpan pstate, Backtraces traces, sass::string msg); - virtual ~InvalidSyntax() throw() {}; - }; - - class NestingLimitError : public Base { - public: - NestingLimitError(SourceSpan pstate, Backtraces traces, sass::string msg = def_nesting_limit); - virtual ~NestingLimitError() throw() {}; - }; - - class DuplicateKeyError : public Base { - protected: - const Map& dup; - const Expression& org; - public: - DuplicateKeyError(Backtraces traces, const Map& dup, const Expression& org); - virtual const char* errtype() const { return "Error"; } - virtual ~DuplicateKeyError() throw() {}; - }; - - class TypeMismatch : public Base { - protected: - const Expression& var; - const sass::string type; - public: - TypeMismatch(Backtraces traces, const Expression& var, const sass::string type); - virtual const char* errtype() const { return "Error"; } - virtual ~TypeMismatch() throw() {}; - }; - - class InvalidValue : public Base { - protected: - const Expression& val; - public: - InvalidValue(Backtraces traces, const Expression& val); - virtual const char* errtype() const { return "Error"; } - virtual ~InvalidValue() throw() {}; - }; - - class StackError : public Base { - protected: - const AST_Node& node; - public: - StackError(Backtraces traces, const AST_Node& node); - virtual const char* errtype() const { return "SystemStackError"; } - virtual ~StackError() throw() {}; - }; - - class EndlessExtendError : public Base { - protected: - const AST_Node& node; - public: - EndlessExtendError(Backtraces traces, const AST_Node& node); - virtual const char* errtype() const { return "EndlessExtendError"; } - virtual ~EndlessExtendError() throw() {}; - }; - - /* common virtual base class (has no pstate or trace) */ - class OperationError : public std::runtime_error { - protected: - sass::string msg; - public: - OperationError(sass::string msg = def_op_msg) - : std::runtime_error(msg.c_str()), msg(msg) - {}; - public: - virtual const char* errtype() const { return "Error"; } - virtual const char* what() const throw() { return msg.c_str(); } - virtual ~OperationError() throw() {}; - }; - - class ZeroDivisionError : public OperationError { - protected: - const Expression& lhs; - const Expression& rhs; - public: - ZeroDivisionError(const Expression& lhs, const Expression& rhs); - virtual const char* errtype() const { return "ZeroDivisionError"; } - virtual ~ZeroDivisionError() throw() {}; - }; - - class IncompatibleUnits : public OperationError { - protected: - // const Sass::UnitType lhs; - // const Sass::UnitType rhs; - public: - IncompatibleUnits(const Units& lhs, const Units& rhs); - IncompatibleUnits(const UnitType lhs, const UnitType rhs); - virtual ~IncompatibleUnits() throw() {}; - }; - - class UndefinedOperation : public OperationError { - protected: - const Expression* lhs; - const Expression* rhs; - const Sass_OP op; - public: - UndefinedOperation(const Expression* lhs, const Expression* rhs, enum Sass_OP op); - // virtual const char* errtype() const { return "Error"; } - virtual ~UndefinedOperation() throw() {}; - }; - - class InvalidNullOperation : public UndefinedOperation { - public: - InvalidNullOperation(const Expression* lhs, const Expression* rhs, enum Sass_OP op); - virtual ~InvalidNullOperation() throw() {}; - }; - - class AlphaChannelsNotEqual : public OperationError { - protected: - const Expression* lhs; - const Expression* rhs; - const Sass_OP op; - public: - AlphaChannelsNotEqual(const Expression* lhs, const Expression* rhs, enum Sass_OP op); - // virtual const char* errtype() const { return "Error"; } - virtual ~AlphaChannelsNotEqual() throw() {}; - }; - - class SassValueError : public Base { - public: - SassValueError(Backtraces traces, SourceSpan pstate, OperationError& err); - virtual ~SassValueError() throw() {}; - }; - - class TopLevelParent : public Base { - public: - TopLevelParent(Backtraces traces, SourceSpan pstate); - virtual ~TopLevelParent() throw() {}; - }; - - class UnsatisfiedExtend : public Base { - public: - UnsatisfiedExtend(Backtraces traces, Extension extension); - virtual ~UnsatisfiedExtend() throw() {}; - }; - - class ExtendAcrossMedia : public Base { - public: - ExtendAcrossMedia(Backtraces traces, Extension extension); - virtual ~ExtendAcrossMedia() throw() {}; - }; - - } - - void warn(sass::string msg, SourceSpan pstate); - void warn(sass::string msg, SourceSpan pstate, Backtrace* bt); - void warning(sass::string msg, SourceSpan pstate); - - void deprecated_function(sass::string msg, SourceSpan pstate); - void deprecated(sass::string msg, sass::string msg2, bool with_column, SourceSpan pstate); - void deprecated_bind(sass::string msg, SourceSpan pstate); - // void deprecated(sass::string msg, SourceSpan pstate, Backtrace* bt); - - void coreError(sass::string msg, SourceSpan pstate); - void error(sass::string msg, SourceSpan pstate, Backtraces& traces); - -} - -#endif diff --git a/src/eval.cpp b/src/eval.cpp index d6540dabfa..547535373a 100644 --- a/src/eval.cpp +++ b/src/eval.cpp @@ -1,1543 +1,3299 @@ -// sass.hpp must go before all system headers to get the -// __EXTENSIONS__ fix on Solaris. -#include "sass.hpp" - -#include -#include -#include -#include -#include -#include - -#include "file.hpp" +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ #include "eval.hpp" -#include "ast.hpp" -#include "bind.hpp" -#include "util.hpp" -#include "inspect.hpp" -#include "operators.hpp" -#include "environment.hpp" -#include "position.hpp" -#include "sass/values.h" -#include "to_value.hpp" -#include "ast2c.hpp" -#include "c2ast.hpp" -#include "context.hpp" -#include "backtrace.hpp" -#include "lexer.hpp" -#include "prelexer.hpp" -#include "parser.hpp" -#include "expand.hpp" -#include "color_maps.hpp" -#include "sass_functions.hpp" -#include "error_handling.hpp" -#include "util_string.hpp" + +#include "cssize.hpp" +#include "sources.hpp" +#include "compiler.hpp" +#include "stylesheet.hpp" +#include "exceptions.hpp" +#include "ast_values.hpp" +#include "ast_imports.hpp" +#include "ast_selectors.hpp" +#include "ast_callables.hpp" +#include "ast_statements.hpp" +#include "ast_expressions.hpp" +#include "parser_selector.hpp" +#include "parser_media_query.hpp" +#include "parser_keyframe_selector.hpp" + +#include "preloader.hpp" + +#include "character.hpp" +#include "calculation.hpp" +#include + +#include "debugger.hpp" namespace Sass { - Eval::Eval(Expand& exp) - : exp(exp), - ctx(exp.ctx), - traces(exp.traces), - force(false), - is_in_comment(false), - is_in_selector_schema(false) + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + Eval::Eval(Compiler& compiler, Logger& logger, bool plainCss) : + logger(logger), + compiler(compiler), + traces(logger), + modctx42(compiler.modctx3), + wconfig(compiler.wconfig), +// extender( +// ExtensionStore::NORMAL, +// logger), + plainCss(plainCss), + inMixin(false), + inFunction(false), + inUnknownAtRule(false), + atRootExcludingStyleRule(false), + inKeyframes(false) { - bool_true = SASS_MEMORY_NEW(Boolean, "[NA]", true); - bool_false = SASS_MEMORY_NEW(Boolean, "[NA]", false); + + mediaStack.push_back({}); + selectorStack.push_back({}); + originalStack.push_back({}); + + bool_true = SASS_MEMORY_NEW(Boolean, SourceSpan::internal("[TRUE]"), true); + bool_false = SASS_MEMORY_NEW(Boolean, SourceSpan::internal("[FALSE]"), false); } - Eval::~Eval() { } - Env* Eval::environment() - { - return exp.environment(); + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + std::set SlashOperands{ + "calc", "clamp", "hypot", "sin", "cos", "tan", "asin", "acos", // + "atan", "sqrt", "exp", "sign", "mod", "rem", "atan2", "pow", "log" + }; + + bool Eval::_operandAllowsSlash(const Expression* node) const { + if (const auto* fn = node->isaFunctionExpression()) { + sass::string name(StringUtils::toLowerCase(fn->name())); + if (fn->ns().empty() && SlashOperands.count(name) > 0) { + return !compiler.varRoot.findFnIdx(name, fn->ns()).isValid(); + } + return false; + } + return true; } - const sass::string Eval::cwd() + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // Helper function for the division + Value* Eval::doDivision(Value* left, Value* right, + BinaryOpExpression* node, Logger& logger, SourceSpan pstate) { - return ctx.cwd(); + // bool allowSlash = node->allowsSlash(); + ValueObj result = left->dividedBy(right, logger, pstate); + if (Number* rv = result->isaNumber()) { + if (left && right && node->allowsSlash() + && _operandAllowsSlash(node->left()) + && _operandAllowsSlash(node->right())) + { + rv->lhsAsSlash(left->isaNumber()); + rv->rhsAsSlash(right->isaNumber()); + // return result.detach(); + } + else { + + // ToDo: deprecation warning + + // return result.detach(); + + // rv->lhsAsSlash({}); // reset + // rv->lhsAsSlash({}); // reset + } + } + return result.detach(); } - struct Sass_Inspect_Options& Eval::options() - { - return ctx.c_options; + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + Value* Eval::withoutSlash(ValueObj value) { + if (value == nullptr) return value; + Number* number = value->isaNumber(); + if (number && number->hasAsSlash()) { + logger.addDeprecation("Using / for division is deprecated and will be removed " + "in LibSass 4.1.0.\n\nRecommendation: math.div(" + number->lhsAsSlash()->inspect() + + ", " + number->rhsAsSlash()->inspect() + ")\n\nMore info and automated migrator: " + "https://sass-lang.com/d/slash-div", value->pstate(), Logger::WARN_MATH_DIV); + } + // Make sure to collect all memory + ValueObj result = value->withoutSlash(); + return result.detach(); } - struct Sass_Compiler* Eval::compiler() + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // Fetch unevaluated positional argument (optionally by name) + // Will error if argument is missing or available both ways + // Note: only needed for lazy evaluation in if expressions + Expression* Eval::getArgument( + ExpressionVector& positional, + ExpressionFlatMap& named, + size_t idx, const EnvKey& name) { - return ctx.c_compiler; + // Try to find the argument by name + auto it = named.find(name); + // Check if requested index is available + if (positional.size() > idx) { + // Check if argument is also known by name + if (it != named.end()) { + // Raise error since it's ambiguous + throw Exception::ArgumentGivenTwice( + logger, name); + } + // Return the positional value + return positional[idx]; + } + else if (it != named.end()) { + // Return the expression + return it->second; + } + // Raise error since nothing was found + throw Exception::MissingArgument( + logger, name); } - EnvStack& Eval::env_stack() + // Fetch evaluated positional argument (optionally by name) + // Will error if argument is missing or available both ways + // Named arguments are consumed and removed from the hash + Value* Eval::getParameter( + ArgumentResults& results, + size_t idx, const Argument* arg) { - return exp.env_stack; + // Try to find the argument by name + auto it = results.named().find(arg->name()); + // Check if requested index is available + if (results.positional().size() > idx) { + // Check if argument is also known by name + if (it != results.named().end()) { + // Raise error since it's ambiguous + throw Exception::ArgumentGivenTwice( + logger, arg->name()); + } + // Return the positional value + return results.positional()[idx]; + } + // Check if argument was found be name + else if (it != results.named().end()) { + // Get value object from hash + // Need to hold onto the object + ValueObj val = it->second; + // Item has been consumed + // Would destroy the value + results.named().erase(it); + // Detach to survive + return val.detach(); + } + // Check if we have default values + else if (!arg->defval().isNull()) { + // Return evaluated expression + return arg->defval()->accept(this); + } + // Raise error since nothing was found + throw Exception::MissingArgument( + logger, arg->name()); } - sass::vector& Eval::callee_stack() + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + //*************************************************// + // Call built-in function with no overloads + //*************************************************// + Value* Eval::_runBuiltInCallable( + CallableArguments* arguments, + BuiltInCallable* callable, + const SourceSpan& pstate) { - return ctx.callee_stack; + ArgumentResults results(_evaluateArguments(arguments)); + const SassFnPair& tuple(callable->callbackFor(results)); + return _callBuiltInCallable(results, tuple, pstate); } - - Expression* Eval::operator()(Block* b) + // EO _runBuiltInCallable + + //*************************************************// + // Call built-in function with overloads + //*************************************************// + Value* Eval::_runBuiltInCallables( + CallableArguments* arguments, + BuiltInCallables* callable, + const SourceSpan& pstate) { - Expression* val = 0; - for (size_t i = 0, L = b->length(); i < L; ++i) { - val = b->at(i)->perform(this); - if (val) return val; - } - return val; + ArgumentResults results(_evaluateArguments(arguments)); + const SassFnPair& tuple(callable->callbackFor(results)); + return _callBuiltInCallable(results, tuple, pstate); } - - Expression* Eval::operator()(Assignment* a) + // EO _runBuiltInCallables + + //*************************************************// + // Helper for _runBuiltInCallable(s) + //*************************************************// + Value* Eval::_callBuiltInCallable( + ArgumentResults& results, + const SassFnPair& function, + const SourceSpan& pstate) { - Env* env = environment(); - sass::string var(a->variable()); - if (a->is_global()) { - if (!env->has_global(var)) { - deprecated( - "!global assignments won't be able to declare new variables in future versions.", - "Consider adding `" + var + ": null` at the top level.", - true, a->pstate()); + + // Here the strategy is to re-use the positional arguments if possible + // In the end we need one continuous array to pass to the built-in callable + // So we need to split out restargs into it's own array, where as in other + // implementations we can re-use positional array for this purpose! + + // Get some items from passed parameters + const SassFnSig& callback(function.second); + const CallableSignature* prototype(function.first); + if (!callback) throw std::runtime_error("Mixin declaration has no callback"); + if (!prototype) throw std::runtime_error("Mixin declaration has no prototype"); + const sass::vector& parameters(prototype->arguments()); + + // Get reference to positional arguments in the result object + // Multiple calls to the same function may re-use the object + ValueVector& positional(results.positional()); + + // Needed here for a specific edge case: restargs must be consumed + // Those can be consumed e.g. by passing them to other functions + // Or simply by calling `keywords` on the rest arguments + ArgumentListObj restargs; + + // If the callable accepts rest argument we can pass all unknown args + // Also if we must pass rest args we must pass only the remaining parts + if (prototype->restArg().empty() == false) { + + // Superfluous function arguments + ValueVector superflous; + + // Check if more arguments provided than parameters + if (positional.size() > parameters.size()) { + // Move superfluous arguments into the array + std::move(positional.begin() + parameters.size(), + positional.end(), back_inserter(superflous)); + // Remove the consumed positional arguments + positional.resize(parameters.size()); } - if (a->is_default()) { - if (env->has_global(var)) { - Expression* e = Cast(env->get_global(var)); - if (!e || e->concrete_type() == Expression::NULL_VAL) { - env->set_global(var, a->value()->perform(this)); - } - } - else { - env->set_global(var, a->value()->perform(this)); - } + + // Try to get named function parameters from argument results + for (size_t i = positional.size(); i < parameters.size(); i += 1) { + positional.push_back(getParameter(results, i, parameters[i])); } - else { - env->set_global(var, a->value()->perform(this)); - } - } - else if (a->is_default()) { - if (env->has_lexical(var)) { - auto cur = env; - while (cur && cur->is_lexical()) { - if (cur->has_local(var)) { - if (AST_Node_Obj node = cur->get_local(var)) { - Expression* e = Cast(node); - if (!e || e->concrete_type() == Expression::NULL_VAL) { - cur->set_local(var, a->value()->perform(this)); - } - } - else { - throw std::runtime_error("Env not in sync"); - } - return 0; - } - cur = cur->parent(); - } - throw std::runtime_error("Env not in sync"); + + // Inherit separator from argument results + SassSeparator separator(results.separator()); + // But make the default a comma instead of spaces + if (separator == SASS_UNDEF) separator = SASS_COMMA; + // Create the rest arguments (move remaining stuff) + restargs = SASS_MEMORY_NEW(ArgumentList, pstate, separator, + std::move(superflous), std::move(results.named())); + // Append last parameter (rest arguments) + positional.emplace_back(restargs); + + } + // Function takes rest arguments, so superfluous arguments must + // be passed to the function via the rest argument array + else { + + // Check that all positional arguments are consumed + if (positional.size() > parameters.size()) { + throw Exception::TooManyArguments(logger, + positional.size(), prototype->maxArgs()); } - else if (env->has_global(var)) { - if (AST_Node_Obj node = env->get_global(var)) { - Expression* e = Cast(node); - if (!e || e->concrete_type() == Expression::NULL_VAL) { - env->set_global(var, a->value()->perform(this)); - } - } + + // Try to get needed function parameters from argument results + for (size_t i = positional.size(); i < parameters.size(); i += 1) { + positional.push_back(getParameter(results, i, parameters[i])); + } + + // Check that all named arguments are consumed + if (results.named().empty() == false) { + throw Exception::TooManyArguments( + logger, results.named()); } - else if (env->is_lexical()) { - env->set_local(var, a->value()->perform(this)); + + } + + for (ValueObj& arg : positional) { + arg = withoutSlash(arg); + } + + // Now execute the built-in function + ValueObj result = callback(pstate, + positional, compiler, + *this); // 7% + + // If we had no rest arguments, this will be true + if (restargs == nullptr) return result.detach(); + // Check if all keywords have been marked consumed, meaning we + // either don't have any or somebody called `keywords` method + if (restargs->hasAllKeywordsConsumed()) return result.detach(); + + // Throw error since not all named arguments were consumed + throw Exception::DuplicateKeyArgument(logger, restargs->keywords()); + // throw Exception::TooManyArguments(logger, restargs->keywords()); + + } + // EO _callBuiltInCallable + + //*************************************************// + // Used for user functions and also by + // mixin includes and content includes. + //*************************************************// + Value* Eval::_runUserDefinedCallable( + CallableArguments* arguments, + UserDefinedCallable* callable, + const SourceSpan& pstate) + { + + // Here the strategy is to put variables on the current function scope + // Therefore we do not really need to results anymore once we set them + // Therefore we can re-use the positional array for our restargs + + // Get some items from passed parameters + CallableDeclaration* declaration(callable->declaration()); + CallableSignature* prototype(declaration->arguments()); + if (!prototype) throw std::runtime_error("Mixin declaration has no prototype"); + const sass::vector& parameters(prototype->arguments()); + + ArgumentResults results(_evaluateArguments(arguments)); + + // Get reference to positional arguments in the result object + // Multiple calls to the same function may re-use the object + ValueVector& positional(results.positional()); + + // Create the variable scope to pass args + auto idxs = callable->declaration()->idxs; + EnvScope scoped(compiler.varRoot, idxs); + + // Try to fetch arguments for all parameters + for (uint32_t i = 0; i < parameters.size(); i += 1) { + // Errors if argument is missing or given twice + ValueObj value = getParameter(results, i, parameters[i]); + // Set lexical variable on scope + compiler.varRoot.setVariable({ idxs, i }, + value->withoutSlash(), false); + } + + // Needed here for a specific edge case: restargs must be consumed + // Those can be consumed e.g. by passing them to other functions + // Or simply by calling `keywords` on the rest arguments + ArgumentListObj restargs; + + // If the callable accepts rest argument we can pass all unknown args + // Also if we must pass rest args we must pass only the remaining parts + if (prototype->restArg().empty() == false) { + + // Remove consumed items (vars already set) + // This will leave the rest arguments behind + if (positional.size() > parameters.size()) { + positional.erase(positional.begin(), + positional.begin() + parameters.size()); } else { - env->set_local(var, a->value()->perform(this)); + positional.clear(); } + + // Inherit separator from argument results + SassSeparator separator(results.separator()); + // But make the default a comma instead of spaces + if (separator == SASS_UNDEF) separator = SASS_COMMA; + // Create the rest arguments (move remaining stuff) + restargs = SASS_MEMORY_NEW(ArgumentList, pstate, separator, + std::move(positional), std::move(results.named())); + // Set last lexical variable on scope + compiler.varRoot.setVariable( + { idxs, (uint32_t)parameters.size() }, + restargs.ptr(), false); + } else { - env->set_lexical(var, a->value()->perform(this)); + + // Check that all positional arguments are consumed + if (positional.size() > parameters.size()) { + throw Exception::TooManyArguments(logger, + positional.size(), parameters.size()); + } + + // Check that all named arguments are consumed + if (results.named().empty() == false) { + throw Exception::TooManyArguments( + logger, results.named()); + } + } - return 0; - } - Expression* Eval::operator()(If* i) + ValueObj result; + // Process all statements within user defined function + // Only the `@return` statement must return something! + for (Statement* statement : declaration->elements()) { + result = statement->accept(this); + if (result != nullptr) break; + } + + // If we had no rest arguments, this will be true + if (restargs == nullptr) return result.detach(); + // Check if all keywords have been marked consumed, meaning we + // either don't have any or somebody called `keywords` method + if (restargs->hasAllKeywordsConsumed()) return result.detach(); + + // Throw error since not all named arguments were consumed + throw Exception::TooManyArguments(logger, restargs->keywords()); + + } + // EO _runUserDefinedCallable + + //*************************************************// + // Call external C-API function + //*************************************************// + Value* Eval::_runExternalCallable( + CallableArguments* arguments, + ExternalCallable* callable, + const SourceSpan& pstate) { - ExpressionObj rv; - Env env(environment()); - env_stack().push_back(&env); - ExpressionObj cond = i->predicate()->perform(this); - if (!cond->is_false()) { - rv = i->block()->perform(this); + + // Here the strategy is to put variables into a sass list of Values + + // Get some items from passed parameters + const EnvKey& name(callable->envkey()); + SassFunctionLambda lambda(callable->lambda()); + CallableSignature* prototype(callable->declaration()); + if (!lambda) throw std::runtime_error("C-API declaration has no callback"); + if (!prototype) throw std::runtime_error("C-API declaration has no prototype"); + const sass::vector& parameters(prototype->arguments()); + + ArgumentResults results(_evaluateArguments(arguments)); + ValueFlatMap& named(results.named()); + ValueVector& positional(results.positional()); + + // Verify that the passed arguments are valid for this function + prototype->verify(positional.size(), named, pstate, traces); + + // Process all prototype items which are not positional + for (size_t i = positional.size(); i < parameters.size(); i++) { + // Try to find name in passed arguments + Argument* argument = parameters[i]; + const auto& name(argument->name()); + const auto& it(named.find(name)); + // Check if we found the name + if (it != named.end()) { + // Append it to our positional args + positional.emplace_back(named[name]); + named.erase(it); // consume argument + } + // Otherwise check if argument has a default value + else if (!argument->defval().isNull()) { + // Evaluate the expression into final value + Value* defval(argument->defval()->accept(this)); + // Append it to our positional args + positional.emplace_back(defval); + } + else { + // This case should never happen due to verification + throw std::runtime_error("Verify did not protect us!"); + } } - else { - Block_Obj alt = i->alternative(); - if (alt) rv = alt->perform(this); + + // Needed here for a specific edge case: restargs must be consumed + // Those can be consumed e.g. by passing them to other functions + // Or simply by calling `keywords` on the rest arguments + ArgumentListObj restargs; + + // If the callable accepts rest argument we can pass all unknown args + // Also if we must pass rest args we must pass only the remaining parts + if (prototype->restArg().empty() == false) { + // Superfluous function arguments + ValueVector superflous; + // Check if more arguments provided than parameters + if (positional.size() > parameters.size()) { + // Move superfluous arguments into the array + std::move(positional.begin() + parameters.size(), + positional.end(), back_inserter(superflous)); + // Remove the consumed positional arguments + positional.resize(parameters.size()); + } + + SassSeparator separator = results.separator(); + if (separator == SASS_UNDEF) separator = SASS_COMMA; + restargs = SASS_MEMORY_NEW(ArgumentList, + prototype->pstate(), separator, + std::move(superflous), std::move(named)); + positional.emplace_back(restargs); + } + + // Create a new sass list holding parameters to pass to function + struct SassValue* c_args = sass_make_list(SASS_COMMA, false); + // First append all positional parameters to it + for (size_t i = 0; i < positional.size(); i++) { + sass_list_push(c_args, Value::wrap(positional[i])); + } + + // Now invoke the function of the callback object + struct SassValue* c_val = (*lambda)( + c_args, compiler.wrap(), callable->cookie()); + // It may not return anything at all + if (c_val == nullptr) return nullptr; + // Unwrap the result into C++ object + ValueObj value(&Value::unwrap(c_val)); + + // Check for some specific return types to handle + // Can't use throw in C code, so this has to do it + if (CustomError* err = value->isaCustomError()) { + sass::string message("C-API function " + + name.orig() + ": " + err->message()); + sass_delete_value(c_args); + sass_delete_value(c_val); + throw Exception::ParserException(traces, message); + } + // This will simply invoke the warning handler + // ToDo: we should have another way to call this + // We might want to warn beside returning a value + else if (CustomWarning* warn = value->isaCustomWarning()) { + sass::string message("C-API function " + + name.orig() + ": " + warn->message()); + // warn->pstate(pstate); + sass_delete_value(c_args); + sass_delete_value(c_val); + logger.addWarning(message, + Logger::WARN_CAPI_FN); } - env_stack().pop_back(); + sass_delete_value(c_val); + sass_delete_value(c_args); + + // If we had no rest arguments, this will be true + if (restargs == nullptr) return value.detach(); + // Check if all keywords have been marked consumed, meaning we + // either don't have any or somebody called `keywords` method + if (restargs->hasAllKeywordsConsumed()) return value.detach(); + + // Throw error since not all named arguments were consumed + throw Exception::TooManyArguments(logger, restargs->keywords()); + + } + // EO _runExternalCallable + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + //*************************************************// + // Call built-in function with no overloads + //*************************************************// + Value* Eval::execute( + BuiltInCallable* callable, + CallableArguments* arguments, + const SourceSpan& pstate) + { + const EnvKey& key(callable->envkey()); + BackTrace trace(pstate, key.orig(), true); + callStackFrame frame(logger, trace); + ValueObj rv = _runBuiltInCallable( + arguments, callable, pstate); + if (rv.isNull()) { + throw Exception::RuntimeException(logger, + "Function finished without @return."); + } + rv = rv->withoutSlash(); return rv.detach(); } - // For does not create a new env scope - // But iteration vars are reset afterwards - Expression* Eval::operator()(ForRule* f) + //*************************************************// + // Call built-in function with overloads + //*************************************************// + Value* Eval::execute( + BuiltInCallables* callable, + CallableArguments* arguments, + const SourceSpan& pstate) { - sass::string variable(f->variable()); - ExpressionObj low = f->lower_bound()->perform(this); - if (low->concrete_type() != Expression::NUMBER) { - traces.push_back(Backtrace(low->pstate())); - throw Exception::TypeMismatch(traces, *low, "integer"); + const EnvKey& key(callable->envkey()); + BackTrace trace(pstate, key.orig(), true); + callStackFrame frame(logger, trace); + ValueObj rv = _runBuiltInCallables(arguments, + callable, pstate); + if (rv.isNull()) { + throw Exception::RuntimeException(logger, + "Function finished without @return."); + } + rv = rv->withoutSlash(); + return rv.detach(); + } + + //*************************************************// + // Used for user functions and also by + // mixin includes and content includes. + //*************************************************// + Value* Eval::execute( + UserDefinedCallable* callable, + CallableArguments* arguments, + const SourceSpan& pstate) + { + RAII_FLAG(inMixin, false); + const EnvKey& key(callable->envkey()); + BackTrace trace(pstate, key.orig(), true); + callStackFrame frame(logger, trace); + ValueObj rv = _runUserDefinedCallable( + arguments, callable, pstate); + if (rv.isNull()) { + throw Exception::RuntimeException(logger, + "Function finished without @return."); + } + rv = rv->withoutSlash(); + return rv.detach(); + } + + //*************************************************// + // Call external C-API function + //*************************************************// + Value* Eval::execute( + ExternalCallable* callable, + CallableArguments* arguments, + const SourceSpan& pstate) + { + const EnvKey& key(callable->envkey()); + BackTrace trace(pstate, key.orig(), true); + callStackFrame frame(logger, trace); + ValueObj rv = _runExternalCallable( + arguments, callable, pstate); + if (rv.isNull()) { + throw Exception::RuntimeException(logger, + "Function finished without @return."); + } + rv = rv->withoutSlash(); + return rv.detach(); + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + ArgumentResults Eval::_evaluateArguments( + CallableArguments* arguments) + { + ArgumentResults results; + // Get some items from passed parameters + ValueFlatMap& named(results.named()); + ValueVector& positional(results.positional()); + + // Collect positional args by evaluating input arguments + positional.reserve(arguments->positional().size() + 1); + for (const auto& arg : arguments->positional()) + { + ValueObj result(arg->accept(this)); + positional.emplace_back(withoutSlash(result)); } - ExpressionObj high = f->upper_bound()->perform(this); - if (high->concrete_type() != Expression::NUMBER) { - traces.push_back(Backtrace(high->pstate())); - throw Exception::TypeMismatch(traces, *high, "integer"); + + // Collect named args by evaluating input arguments + for (const auto& kv : arguments->named()) { + ValueObj result(kv.second->accept(this)); + named.insert(std::make_pair(kv.first, + withoutSlash(result))); } - Number_Obj sass_start = Cast(low); - Number_Obj sass_end = Cast(high); - // check if units are valid for sequence - if (sass_start->unit() != sass_end->unit()) { - sass::ostream msg; msg << "Incompatible units: '" - << sass_end->unit() << "' and '" - << sass_start->unit() << "'."; - error(msg.str(), low->pstate(), traces); + + // Abort if we don't take any restargs + if (arguments->restArg() == nullptr) { + // ToDo: no test case for this!? + results.separator(SASS_UNDEF); + return results; } - double start = sass_start->value(); - double end = sass_end->value(); - // only create iterator once in this environment - Env env(environment(), true); - env_stack().push_back(&env); - Block_Obj body = f->block(); - Expression* val = 0; - if (start < end) { - if (f->is_inclusive()) ++end; - for (double i = start; - i < end; - ++i) { - Number_Obj it = SASS_MEMORY_NEW(Number, low->pstate(), i, sass_end->unit()); - env.set_local(variable, it); - val = body->perform(this); - if (val) break; - } - } else { - if (f->is_inclusive()) --end; - for (double i = start; - i > end; - --i) { - Number_Obj it = SASS_MEMORY_NEW(Number, low->pstate(), i, sass_end->unit()); - env.set_local(variable, it); - val = body->perform(this); - if (val) break; + + // Evaluate the variable expression ( + ValueObj result = arguments->restArg()->accept(this); + ValueObj rest = withoutSlash(result); + + SassSeparator separator = SASS_UNDEF; + + if (Map* restMap = rest->isaMap()) { + _addRestValueMap(named, restMap, + arguments->restArg()->pstate()); + } + else if (List* list = rest->isaList()) { + std::copy(list->begin(), list->end(), + std::back_inserter(positional)); + separator = list->separator(); + if (ArgumentList* args = rest->isaArgumentList()) { + auto& kwds = args->keywords(); + for (const auto& kv : kwds) { + named[kv.first] = kv.second; + } } } - env_stack().pop_back(); - return val; + else { + positional.emplace_back(std::move(rest)); + } + + if (arguments->kwdRest() == nullptr) { + results.separator(separator); + return results; + } + + // kwdRest already poisoned + ValueObj keywordRest = arguments->kwdRest()->accept(this); + + if (Map* restMap = keywordRest->isaMap()) { + _addRestValueMap(named, restMap, arguments->kwdRest()->pstate()); + results.separator(separator); + return results; + } + + callStackFrame csf(logger, keywordRest->pstate()); + throw Exception::RuntimeException(traces, + "Variable keyword arguments must be a map (was $keywordRest)."); + } - // Eval does not create a new env scope - // But iteration vars are reset afterwards - Expression* Eval::operator()(EachRule* e) + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + /// Evaluates [expression] and calls `toCss()`. + sass::string Eval::toCss(Expression* expression, bool quote) + { + ValueObj value = expression->accept(this); + return value->toCss(quote); + } + + /// Evaluates [interpolation] into a serialized string. + /// + /// If [trim] is `true`, removes whitespace around the result. + /// If [warnForColor] is `true`, this will emit a warning for + /// any named color values passed into the interpolation. + sass::string Eval::acceptInterpolation(InterpolationObj interpolation, bool warnForColor, bool trim) { - sass::vector variables(e->variables()); - ExpressionObj expr = e->list()->perform(this); - Env env(environment(), true); - env_stack().push_back(&env); - List_Obj list; - Map* map = nullptr; - if (expr->concrete_type() == Expression::MAP) { - map = Cast(expr); + // Needed in loop + ValueObj value; + // Create CSS output options + OutputOptions out( + SASS_STYLE_TO_CSS, + compiler.precision); + // Create the emitter + Cssize cssize(out); + // Don't quote strings + cssize.quotes = false; + + RAII_FLAG(inSupportsDeclaration, false); + // Process all interpolants in the interpolation + // Items in interpolations are only of three types + // Performance optimized since it's used quite a lot + for (Interpolant* itpl : interpolation->elements()) { + if (itpl == nullptr) continue; + switch (itpl->getType()) { + case Interpolant::LiteralInterpolant: + cssize.append_token( + static_cast(itpl)->text(), + static_cast(itpl)); + break; + case Interpolant::ValueInterpolant: + static_cast(itpl) + ->accept(&cssize); + break; + case Interpolant::ExpressionInterpolant: + value = static_cast(itpl)->accept(this); + if (warnForColor) { + if (Color* color = value->isaColor()) { + ColorRgbaObj rgba = color->toRGBA(); + double numval = rgba->r() * 0x10000 + + rgba->g() * 0x100 + rgba->b(); + if (const char* disp = color_to_name((int)numval)) { + sass::sstream msg; + msg << "You probably don't mean to use the color value "; + msg << disp << " in interpolation here.\nIt may end up represented "; + msg << "as " << rgba->inspect() <<", which will likely produce invalid "; + msg << "CSS. Always quote color names when using them as strings or map "; + msg << "keys (for example, \"" << disp << "\"). If you really want to "; + msg << "use the color value, append it to an empty string first to avoid "; + msg << "this warning (for example, '\"\" + " << disp << "')."; + logger.addWarning(msg.str(), itpl->pstate(), Logger::WARN_COLOR_ITPL); + } + } + } + value->accept(&cssize); + break; + } } - else if (SelectorList * ls = Cast(expr)) { - ExpressionObj rv = Listize::perform(ls); - list = Cast(rv); + // ToDo: check it's using RVO + return cssize.get_buffer(trim); + } + // EO acceptInterpolation + + /// Evaluates [interpolation] and wraps the result in a [SourceData]. + /// + /// If [trim] is `true`, removes whitespace around the result. + /// If [warnForColor] is `true`, this will emit a warning for + /// any named color values passed into the interpolation. + SourceData* Eval::interpolationToSource(InterpolationObj interpolation, bool warnForColor, bool trim, bool ws) + { + if (interpolation.isNull()) return nullptr; + // pstate has 13 with ending + sass::string result = acceptInterpolation(interpolation, warnForColor, trim); + // Check if white-space only is disallowed; check and possibly abort + if (!ws && StringUtils::isWhitespaceOnly(result)) return nullptr; + // if (!ws && std::find(result.begin(), result.end(), std::isspace) == result.end()) return nullptr; + return SASS_MEMORY_NEW(SourceItpl, interpolation->pstate(), std::move(result)); + } + + /// Evaluates [interpolation] and wraps the result in a [CssValue]. + /// + /// If [trim] is `true`, removes whitespace around the result. + /// If [warnForColor] is `true`, this will emit a warning for + /// any named color values passed into the interpolation. + CssString* Eval::interpolationToCssString(InterpolationObj interpolation, + bool warnForColor, bool trim) + { + if (interpolation.isNull()) return nullptr; + sass::string result = acceptInterpolation(interpolation, warnForColor, trim); + return SASS_MEMORY_NEW(CssString, interpolation->pstate(), std::move(result)); + } + + /// Evaluates [interpolation] and parses the result into a [SelectorList]. + SelectorListObj Eval::interpolationToSelector(Interpolation* itpl, bool plainCss, bool allowParent) + { + // Create a new source data object from the evaluated interpolation + if (SourceDataObj synthetic = interpolationToSource(itpl, true, false, false)) { + // Everything parsed, will be parsed from perspective of local content + // Pass the source-map in for the interpolation, so the scanner can + // update the positions according to previous source-positions + // Is a parser state solely represented by a source map or do we + // need an intermediate format for them? + SelectorParser parser(compiler, synthetic); + parser.allowPlaceholder = plainCss == false; + parser.allowParent = allowParent && plainCss == false; + return parser.parseSelectorList(); // comes detached! + } + // Otherwise interpolation resulted in white-space only + callStackFrame frame(compiler, BackTrace(itpl->pstate())); + throw Exception::ParserException(compiler, "expected selector."); + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + void Eval::_evaluateMacroArguments( + CallableArguments* arguments, + ExpressionVector& positional, + ExpressionFlatMap& named) + { + + if (arguments->restArg()) { + + ValueObj rest = arguments->restArg()->accept(this); + + if (Map* restMap = rest->isaMap()) { + _addRestExpressionMap(named, restMap, arguments->restArg()->pstate()); + } + else if (List* restList = rest->isaList()) { + for (const ValueObj& value : restList->elements()) { + positional.emplace_back(SASS_MEMORY_NEW( + ValueExpression, value->pstate(), value)); + } + // separator = list->separator(); + if (ArgumentList* args = rest->isaArgumentList()) { + for (auto& kv : args->keywords()) { + named[kv.first] = SASS_MEMORY_NEW(ValueExpression, + kv.second->pstate(), kv.second); + } + } + } + else { + positional.emplace_back(SASS_MEMORY_NEW( + ValueExpression, rest->pstate(), rest)); + } + } - else if (expr->concrete_type() != Expression::LIST) { - list = SASS_MEMORY_NEW(List, expr->pstate(), 1, SASS_COMMA); - list->append(expr); + + if (arguments->kwdRest() == nullptr) { + return; } - else { - list = Cast(expr); + + ValueObj keywordRest = arguments->kwdRest()->accept(this); + + if (Map* restMap = keywordRest->isaMap()) { + _addRestExpressionMap(named, restMap, arguments->restArg()->pstate()); + return; } - Block_Obj body = e->block(); - ExpressionObj val; + throw Exception::RuntimeException(logger, + "Variable keyword arguments must be a map (was $keywordRest)."); - if (map) { - for (ExpressionObj key : map->keys()) { - ExpressionObj value = map->at(key); + } + // EO _evaluateMacroArguments - if (variables.size() == 1) { - List* variable = SASS_MEMORY_NEW(List, map->pstate(), 2, SASS_SPACE); - variable->append(key); - variable->append(value); - env.set_local(variables[0], variable); - } else { - env.set_local(variables[0], key); - env.set_local(variables[1], value); - } + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// - val = body->perform(this); - if (val) break; + // CssStylesheet _combineCss(Module root, { bool clone = false }) { + // void _extendModules(List> sortedModules) { + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + Value* Eval::visitBooleanExpression(BooleanExpression* ex) + { + #ifdef SASS_ELIDE_COPIES + return ex->value(); + #else + return SASS_MEMORY_COPY(ex->value()); + #endif + } + + Value* Eval::visitColorExpression(ColorExpression* ex) + { + #ifdef SASS_ELIDE_COPIES + return ex->value(); + #else + ColorObj color = ex->value(); + ColorObj copy = SASS_MEMORY_COPY(color); + copy->disp(color->disp()); + return copy.detach(); +#endif + } + + Value* Eval::visitNumberExpression(NumberExpression* ex) + { + #ifdef SASS_ELIDE_COPIES + return ex->value(); + #else + return SASS_MEMORY_COPY(ex->value()); + #endif + } + + Value* Eval::visitNullExpression(NullExpression* ex) + { + #ifdef SASS_ELIDE_COPIES + return ex->value(); + #else + return SASS_MEMORY_COPY(ex->value()); + #endif + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + //*************************************************// + //*************************************************// + Value* Eval::visitListExpression(ListExpression* l) + { + // regular case for unevaluated lists + ListObj ll = SASS_MEMORY_NEW(List, l->pstate(), + ValueVector(), l->separator()); + ll->hasBrackets(l->hasBrackets()); + for (size_t i = 0, L = l->size(); i < L; ++i) { + ll->append(l->get(i)->accept(this)); + } + return ll.detach(); + } + // EO visitListExpression + + //*************************************************// + //*************************************************// + Value* Eval::visitMapExpression(MapExpression* m) + { + ValueObj key; + MapObj map(SASS_MEMORY_NEW(Map, m->pstate())); + const ExpressionVector& kvlist(m->kvlist()); + for (size_t i = 0, L = kvlist.size(); i < L; i += 2) + { + // First evaluate the key + key = kvlist[i]->accept(this); + // Check for key duplication + if (map->has(key)) { + traces.emplace_back(kvlist[i]->pstate()); + throw Exception::DuplicateKeyError(traces, *map, *key); } + // Second insert the evaluated value for key + map->insertOrSet(key, kvlist[i + 1]->accept(this)); } - else { - if (list->length() == 1 && Cast(list)) { - list = Cast(list); - } - for (size_t i = 0, L = list->length(); i < L; ++i) { - Expression* item = list->at(i); - // unwrap value if the expression is an argument - if (Argument* arg = Cast(item)) item = arg->value(); - // check if we got passed a list of args (investigate) - if (List* scalars = Cast(item)) { - if (variables.size() == 1) { - Expression* var = scalars; - env.set_local(variables[0], var); - } else { - // https://github.com/sass/libsass/issues/3078 - for (size_t j = 0, K = variables.size(); j < K; ++j) { - env.set_local(variables[j], j >= scalars->length() - ? SASS_MEMORY_NEW(Null, expr->pstate()) : scalars->at(j)); - } - } - } else { - if (variables.size() > 0) { - env.set_local(variables.at(0), item); - for (size_t j = 1, K = variables.size(); j < K; ++j) { - // XXX: this is never hit via spec tests - Expression* res = SASS_MEMORY_NEW(Null, expr->pstate()); - env.set_local(variables[j], res); - } - } + return map.detach(); + } + // EO visitMapExpression + + //*************************************************// + //*************************************************// + Value* Eval::visitStringExpression(StringExpression* node) + { + // Don't use [performInterpolation] here because we need to get + // the raw text from strings, rather than the semantic value. + InterpolationObj itpl = node->text(); + sass::vector strings; + RAII_FLAG(inSupportsDeclaration, false); + for (const auto& item : itpl->elements()) { + if (ItplString* lit = item->isaItplString()) { + strings.emplace_back(lit->text()); + } + else { + ValueObj result = item->isaValue(); + if (Expression* ex = item->isaExpression()) { + result = ex->accept(this); + } + if (String* lit = result->isaString()) { + strings.emplace_back(lit->value()); + } + else if (!result->isNull()) { + strings.emplace_back(result->toCss(false)); } - val = body->perform(this); - if (val) break; } } - env_stack().pop_back(); - return val.detach(); + + return SASS_MEMORY_NEW(String, node->pstate(), + StringUtils::join(strings, ""), node->hasQuotes()); + } + // EO visitStringExpression + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + Value* Eval::visitSupportsExpression(SupportsExpression* expr) + { + sass::string text(_visitSupportsCondition(expr->condition())); + return new String(expr->pstate(), std::move(text)); } - Expression* Eval::operator()(WhileRule* w) + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + //*************************************************// + //*************************************************// + Value* Eval::visitBinaryOpExpression(BinaryOpExpression* node) { - ExpressionObj pred = w->predicate(); - Block_Obj body = w->block(); - Env env(environment(), true); - env_stack().push_back(&env); - ExpressionObj cond = pred->perform(this); - while (!cond->is_false()) { - ExpressionObj val = body->perform(this); - if (val) { - env_stack().pop_back(); - return val.detach(); + + Import* imp = compiler.import_stack.back(); + bool isPlainCss = imp->syntax == SASS_IMPORT_CSS; + + if (isPlainCss) { + if (node->operand() != SassOperator::ASSIGN) { + if (node->operand() != SassOperator::DIV) { + // callStackFrame frame(compiler, node->pstate()); + callStackFrame frame2(compiler, node->opstate()); + throw Exception::SassScriptException(logger, node->pstate(), + "Operators aren't allowed in plain CSS."); + } } - cond = pred->perform(this); } - env_stack().pop_back(); - return 0; + ValueObj left, right; + Expression* lhs = node->left(); + Expression* rhs = node->right(); + left = lhs->accept(this); + switch (node->operand()) { + case SassOperator::IESEQ: + right = rhs->accept(this); + return left->singleEquals( + right, logger, node->pstate()); + case SassOperator::OR: + if (left->isTruthy()) { + return left.detach(); + } + return rhs->accept(this); + case SassOperator::AND: + if (!left->isTruthy()) { + return left.detach(); + } + return rhs->accept(this); + case SassOperator::EQ: + right = rhs->accept(this); + return ObjEqualityFn(left, right) + ? bool_true : bool_false; + case SassOperator::NEQ: + right = rhs->accept(this); + return ObjEqualityFn(left, right) + ? bool_false : bool_true; + case SassOperator::GT: + right = rhs->accept(this); + return left->greaterThan(right, + logger, node->pstate()) + ? bool_true : bool_false; + case SassOperator::GTE: + right = rhs->accept(this); + return left->greaterThanOrEquals(right, + logger, node->pstate()) + ? bool_true : bool_false; + case SassOperator::LT: + right = rhs->accept(this); + return left->lessThan(right, + logger, node->pstate()) + ? bool_true : bool_false; + case SassOperator::LTE: + right = rhs->accept(this); + return left->lessThanOrEquals(right, + logger, node->pstate()) + ? bool_true : bool_false; + case SassOperator::ADD: + right = rhs->accept(this); + return left->plus(right, + logger, node->pstate()); + case SassOperator::SUB: + right = rhs->accept(this); + return left->minus(right, + logger, node->pstate()); + case SassOperator::MUL: + right = rhs->accept(this); + return left->times(right, + logger, node->pstate()); + case SassOperator::DIV: + right = rhs->accept(this); + return doDivision(left, right, + node, logger, node->pstate()); + case SassOperator::MOD: + right = rhs->accept(this); + return left->modulo(right, + logger, node->pstate()); + //case SassOperator::ASSIGN: + // throw "Assign not implemented"; + } + // Satisfy compiler + return nullptr; } + // visitBinaryOpExpression - Expression* Eval::operator()(Return* r) + //*************************************************// + //*************************************************// + Value* Eval::visitUnaryOpExpression(UnaryOpExpression* node) { - return r->value()->perform(this); + ValueObj operand = node->operand()->accept(this); + switch (node->optype()) { + case UnaryOpType::PLUS: + return operand->unaryPlus(logger, node->pstate()); + case UnaryOpType::MINUS: + return operand->unaryMinus(logger, node->pstate()); + case UnaryOpType::NOT: + return operand->unaryNot(logger, node->pstate()); + case UnaryOpType::SLASH: + return operand->unaryDivide(logger, node->pstate()); + } + // Satisfy compiler + return nullptr; } + // EO visitUnaryOpExpression + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// - Expression* Eval::operator()(WarningRule* w) + // This operates similar to a function call + Value* Eval::visitIfExpression(IfExpression* node) { - Sass_Output_Style outstyle = options().output_style; - options().output_style = NESTED; - ExpressionObj message = w->message()->perform(this); - Env* env = environment(); + CallableArguments* arguments = node->arguments(); + callStackFrame frame(logger, node->pstate()); + // We need to make copies here to preserve originals + // We could optimize this further, but impact is slim + ExpressionFlatMap named(arguments->named()); + ExpressionVector positional(arguments->positional()); + // Rest arguments must be evaluated in all cases + // evaluateMacroArguments is only used for this + _evaluateMacroArguments(node->arguments(), positional, named); + ExpressionObj condition = getArgument(positional, named, 0, Keys::condition); + ExpressionObj ifTrue = getArgument(positional, named, 1, Keys::ifTrue); + ExpressionObj ifFalse = getArgument(positional, named, 2, Keys::ifFalse); + if (positional.size() > 3) { + throw Exception::TooManyArguments( + logger, positional.size(), 3); + } + if (positional.size() + named.size() > 3) { + throw Exception::TooManyArguments(logger, named, + { Keys::condition, Keys::ifTrue, Keys::ifFalse }); + } + + ValueObj rv = condition ? condition->accept(this) : nullptr; + Expression* ex = rv && rv->isTruthy() ? ifTrue : ifFalse; + if (ex == nullptr) return nullptr; + ValueObj result(ex->accept(this)); + return withoutSlash(result); + } - // try to use generic function - if (env->has("@warn[f]")) { + Value* Eval::visitParenthesizedExpression(ParenthesizedExpression* ex) + { + Import* imp = compiler.import_stack.back(); + bool isPlainCss = imp->syntax == SASS_IMPORT_CSS; - // add call stack entry - callee_stack().push_back({ - "@warn", - w->pstate().getPath(), - w->pstate().getLine(), - w->pstate().getColumn(), - SASS_CALLEE_FUNCTION, - { env } - }); + if (isPlainCss) { + callStackFrame frame(traces, ex->pstate()); + throw Exception::RuntimeException(logger, + "Parentheses aren't allowed in plain CSS."); + } - 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); - - AST2C ast2c; - union Sass_Value* c_args = sass_make_list(1, SASS_COMMA, false); - sass_list_set_value(c_args, 0, message->perform(&ast2c)); - union Sass_Value* c_val = c_func(c_args, c_function, compiler()); - options().output_style = outstyle; - callee_stack().pop_back(); - sass_delete_value(c_args); - sass_delete_value(c_val); - return 0; + // return ex->expression(); + if (ex->expression()) { + return ex->expression()->accept(this); + } + return nullptr; + } + Value* Eval::visitSelectorExpression(SelectorExpression* p) + { + if (SelectorListObj& parents = original()) { + return parents->toValue(); + } + else { + return SASS_MEMORY_NEW(Null, p->pstate()); } + } + + void Eval::renderArgumentInvocation(sass::string& strm, CallableArguments* args) + { + if (!args->named().empty()) { + callStackFrame frame(traces, + args->pstate()); + throw Exception::RuntimeException(logger, + "Plain CSS functions don't support keyword arguments."); + } + if (args->kwdRest() != nullptr) { + callStackFrame frame(traces, + args->kwdRest()->pstate()); + throw Exception::RuntimeException(logger, + "Plain CSS functions don't support keyword arguments."); + } + bool addComma = false; + strm += "("; + for (Expression* argument : args->positional()) { + if (addComma) { strm += ", "; } + else { addComma = true; } + strm += toCss(argument); + } + if (ExpressionObj rest = args->restArg()) { + if (addComma) { strm += ", "; } + else { addComma = true; } + strm += toCss(rest); + } + strm += ")"; + } - sass::string result(unquote(message->to_sass())); - std::cerr << "WARNING: " << result << std::endl; - traces.push_back(Backtrace(w->pstate())); - std::cerr << traces_to_string(traces, " "); - std::cerr << std::endl; - options().output_style = outstyle; - traces.pop_back(); - return 0; + Value* Eval::visitItplFnExpression(ItplFnExpression* cssfn) + { + // return ex->expression(); + if (cssfn->itpl()) { + sass::string strm; + strm += acceptInterpolation(cssfn->itpl(), false); + renderArgumentInvocation(strm, cssfn->arguments()); + return SASS_MEMORY_NEW( + String, cssfn->pstate(), + std::move(strm)); + } + return nullptr; } - Expression* Eval::operator()(ErrorRule* e) + Value* Eval::visitValueExpression(ValueExpression* node) { - Sass_Output_Style outstyle = options().output_style; - options().output_style = NESTED; - ExpressionObj message = e->message()->perform(this); - Env* env = environment(); + // We have a bug lurking somewhere + // without detach it gets deleted? + ValueObj value = node->value(); + return value.detach(); + } - // try to use generic function - if (env->has("@error[f]")) { + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// - // add call stack entry - callee_stack().push_back({ - "@error", - e->pstate().getPath(), - e->pstate().getLine(), - e->pstate().getColumn(), - SASS_CALLEE_FUNCTION, - { env } - }); + Value* Eval::visitMixinRule(MixinRule* rule) + { + UserDefinedCallableObj callable = + SASS_MEMORY_NEW(UserDefinedCallable, + rule->pstate(), rule->name(), rule, nullptr); + rule->midx(compiler.varRoot.findMixIdx( + rule->name(), Strings::empty)); + compiler.varRoot.setMixin( + rule->midx(), callable, false); + return nullptr; + } - Definition* def = Cast((*env)["@error[f]"]); - // Block_Obj body = def->block(); - // Native_Function func = def->native_function(); - Sass_Function_Entry c_function = def->c_function(); - Sass_Function_Fn c_func = sass_function_get_function(c_function); - - AST2C ast2c; - union Sass_Value* c_args = sass_make_list(1, SASS_COMMA, false); - sass_list_set_value(c_args, 0, message->perform(&ast2c)); - union Sass_Value* c_val = c_func(c_args, c_function, compiler()); - options().output_style = outstyle; - callee_stack().pop_back(); - sass_delete_value(c_args); - sass_delete_value(c_val); - return 0; - } + Value* Eval::visitFunctionRule(FunctionRule* rule) + { + UserDefinedCallableObj callable = + SASS_MEMORY_NEW(UserDefinedCallable, + rule->pstate(), rule->name(), rule, nullptr); + rule->fidx(compiler.varRoot.findFnIdx( + rule->name(), Strings::empty)); + compiler.varRoot.setFunction( + rule->fidx(), callable, false); + return nullptr; + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + //*************************************************// + // Evaluate and return lexical variable with `name` + // Cache dynamic lookup results in `vidxs` member + //*************************************************// + Value* Eval::visitVariableExpression(VariableExpression* variable) + { + + // Check if variable expression was already resolved + if (variable->vidxs().empty()) { + // Is variable on the local scope? + if (variable->isLexical()) { + // Find all idxs and fill vidxs + compiler.varRoot.findVarIdxs( + variable->vidxs(), + variable->name()); + } + // Variable is on module (root) scope + else { + EnvRef vidx = compiler.varRoot.findVarIdx( + variable->name(), variable->ns()); + if (vidx.isValid()) variable->vidxs().push_back(vidx); + } + + } + + // Variables must be resolved from top to bottom + // This has to do with the way how Sass handles scopes + // E.g. in loops, the variable can first point to the outer + // variable and later to the inner variable, if an assignment + // exists after the first reference in that loop scope: + // $a: 0; @for $i from 1 through 3 { @debug $a; $a: $i; } @debug $a + // $b: 0; a { @for $i from 1 through 3 { @debug $b; $b: $i; } @debug $b } + for (const EnvRef& vidx : variable->vidxs()) { + auto& value = compiler.varRoot.getVariable(vidx); + if (value != nullptr) return value->withoutSlash(); + } + + // If we reach this point we have an error + // Mixin wasn't found and couldn't be executed + callStackFrame frame(traces, variable->pstate()); + + // Check if variable was requested from a module and if that module actually exists + if (variable->ns().empty() || compiler.varRoot.stack.back()->hasNameSpace(variable->ns())) { + throw Exception::RuntimeException(traces, "Undefined variable."); + } + + // Otherwise the module simply wasn't imported + throw Exception::ModuleUnknown(traces, variable->ns()); + + } + // EO visitVariableExpression + + //*************************************************// + // Execute function with `name` and return a Value + // Cache dynamic lookup results in `fidx` member + //*************************************************// + Value* Eval::visitFunctionExpression(FunctionExpression* function) + { + + //std::cerr << "+ visitFunctionExpression " + function->name() << "\n"; + + // Check if function expression was already resolved + if (!function->fidx().isValid()) { + // Try to fetch the function by finding it by name + // This may fail, as function expressions can also be + // css functions if the function by name is not declared. + function->fidx(compiler.varRoot.findFnIdx(function->name(), function->ns())); + } + + const sass::string& fname(function->name()); + sass::string name(StringUtils::toLowerCase(fname)); + const auto& args = function->arguments(); + const auto& list = args->positional(); + Callable* callable = nullptr; + + if (function->fidx().isValid()) + { + callable = compiler.varRoot.getFunction(function->fidx()); + //if (callable && callable->isInternal()) + // if (function->ns().empty()) + // callable = nullptr; + } + + Import* imp = compiler.import_stack.back(); + bool isPlainCss = imp->syntax == SASS_IMPORT_CSS; + + if (!callable && !function->ns().empty()) + { + + callStackFrame frame(traces, function->pstate()); + throw Exception::RuntimeException(traces, + "Undefined function."); + + /* + bool hasModule = false; + // modctx42->upstream + for (auto cur : compiler.varStack3312) { + auto asd = cur->module->moduse.find(function->ns()); + if (cur->module->moduse.count(function->ns()) != 0) { + callStackFrame frame(traces, function->pstate()); + throw Exception::RuntimeException(traces, + "Undefined function."); + hasModule = true; + break; + } + } + + callStackFrame frame(traces, function->pstate()); + throw Exception::RuntimeException(traces, + "There is no module with the namespace \"" + function->ns() + "\"."); + */ + } + + if (!callable || (callable->isInternal() && function->ns().empty())) { + + // Check for potential css replacement + if (args->named().empty() && args->restArg().isNull()) + { + if (name == str_min || name == str_max + || name == str_round || name == str_abs) + { + // Check that every argument is safe + if (std::all_of(list.begin(), list.end(), + [&](const ExpressionObj& expression) { + return expression->isCalcSafe(); })) + { + return visitCalcuation(name, function, true); + } + } + } + + if (name == str_calc || name == str_clamp || name == str_hypot + || name == str_sin || name == str_cos || name == str_tan + || name == str_asin || name == str_acos || name == str_atan + || name == str_sqrt || name == str_exp || name == str_sign + || name == str_mod || name == str_rem || name == str_atan2 + || name == str_pow || name == str_log) + { + return visitCalcuation(name, function, false); + } + + if (isPlainCss == false) { + // std::cerr << "Must2 create PlainCss callable\n"; + // callable = SASS_MEMORY_NEW(PlainCssCallable, + // function->pstate(), function->name()); + // try to find via internal functions and name + //for (auto in : compiler.varRoot.intFunction) { + // if (in && in->name() == name) { + // callable = in; + // } + //} + } + + if (!callable) + { + // Convert to css function + sass::string strm; + strm += function->name(); + renderArgumentInvocation( + strm, function->arguments()); + return SASS_MEMORY_NEW( + String, function->pstate(), + std::move(strm)); + } + + } + else if (isPlainCss) { + std::cerr << "Must create PlainCss callable\n"; + callable = SASS_MEMORY_NEW(PlainCssCallable, + function->pstate(), function->name()); + } + + // Check if function is already defined on the frame/scope + // Can fail if the function definition comes after the usage + if (callable) + { + RAII_FLAG(inFunction, true); + callStackFrame frame(traces, function->pstate(), true); + return callable->execute(*this, + args, function->pstate()); + } + + // Only functions without namespace can be css-functions + // Functions with namespace must be executed or fail + + // If we reach this point we have an error + // Mixin wasn't found and couldn't be executed + callStackFrame frame(traces, function->pstate()); + // Otherwise the module simply wasn't imported + throw Exception::ModuleUnknown(traces, function->ns()); + + // ToDo: check if we reach this branch + + // Check if function was requested from a module and if that module actually exists + //if (function->ns().empty() || compiler.varRoot.stack.back()->hasNameSpace(function->ns())) { + // throw Exception::RuntimeException(traces, "Do we hit this branch?."); + //} + + + } + // EO visitFunctionExpression + + void _checkWhitespaceAroundCalculationOperator(BinaryOpExpression* node) + { + if (node->operand() != SassOperator::ADD + && node->operand() != SassOperator::SUB) return; + + + } + + void Eval::_checkAdjacentCalculationValues(const ValueVector& elements, const ListExpression* node) + { + for (size_t i = 1; i < elements.size(); i++) { + auto previous = elements[i - 1]; + auto current = elements[i]; + if (previous->isaString() || current->isaString()) continue; + + auto previousNode = node->items()[i - 1]; + auto currentNode = node->items()[i]; + + if (auto op = currentNode->isaUnaryOpExpression()) { + auto foo = op->optype(); + if ((op->optype() != UnaryOpType::PLUS) || (op->optype() != UnaryOpType::MINUS)) continue; + throw Exception::OpNotCalcSafe(traces, op); + } + else if (auto nr = currentNode->isaNumberExpression()) { + if (nr->value()->value() >= 0) { + throw Exception::MissingMathOp(traces, previousNode, currentNode); + } + throw Exception::OpNotCalcSafe(traces, nr); + } + + throw Exception::MissingMathOp(traces, previousNode, currentNode); + + + // `calc(1 -2)` parses as a space-separated list whose second value is a + // unary operator or a negative number, but just saying it's an invalid + // expression doesn't help the user understand what's going wrong. We + // add special case error handling to help clarify the issue. + // throw _exception( + // '"+" and "-" must be surrounded by whitespace in calculations.', + // currentNode.span.subspan(0, 1)); + //} + //else { + // throw _exception('Missing math operator.', + // previousNode.span.expand(currentNode.span)); + //} + } + + } + + Value* Eval::_visitCalculationExpression(Expression* node, bool inLegacySassFunction) + { + // std::cerr << "visit calc exp " << node->toString() << "\n"; + if (auto inner = node->isaParenthesizedExpression()) { + // std::cerr << " eval parenthisez\n"; + auto result = _visitCalculationExpression(inner->expression(), inLegacySassFunction); + if (result->isaString()) return SASS_MEMORY_NEW(String, + inner->pstate(), "(" + result->inspect() + ")"); + else return result; + } + else if (auto inner = node->isaStringExpression()) { + if (inner->isCalcSafe()) { + // if (node.isCalculationSafe) + // assert(!nod_visitCalculationExpressione.hasQuotes); + sass::string text(inner->text()->getPlainString()); + StringUtils::makeLowerCase(text); + if (text == str_pi) return SASS_MEMORY_NEW(Number, inner->pstate(), Constants::Math::M_PI); + else if (text == str_e) return SASS_MEMORY_NEW(Number, inner->pstate(), Constants::Math::M_E); + else if (text == str_infinity) return SASS_MEMORY_NEW(Number, inner->pstate(), std::numeric_limits::infinity()); + else if (text == str_neg_infinity) return SASS_MEMORY_NEW(Number, inner->pstate(), -std::numeric_limits::infinity()); + else if (text == str_nan) return SASS_MEMORY_NEW(Number, inner->pstate(), std::numeric_limits::quiet_NaN()); + else { return SASS_MEMORY_NEW(String, inner->pstate(), acceptInterpolation(inner->text(), false), false); } + } + else { + callStackFrame frame(traces, inner->pstate()); + throw Exception::SassScriptException( + "This expression can't be used in a calculation.", + traces, inner->pstate()); + } + } + else if (node->isaNumberExpression() + || node->isaVariableExpression() + || node->isaFunctionExpression() + || node->isaIfExpression()) + { + //std::cerr << "Process expression\n"; + ValueObj result = node->accept(this); + if (result->isaNumber()) { + return result.detach(); + } + if (result->isaCalculation()) { + return result.detach(); + } + if (auto str = result->isaString()) { + if (str->hasQuotes() == false) + return result.detach(); + } + //std::cerr << "cant be used in calculon\n"; + } + else if (auto inner = node->isaBinaryOpExpression()) { + + //std::cerr << "Process binary op\n"; + // _checkWhitespaceAroundCalculationOperator(node); + if (inner->isCalcSafeOp() == false) { + if (inner->operand() == SassOperator::ADD) + throw Exception::OpNotCalcSafe(traces, inner); + if (inner->operand() == SassOperator::SUB) + throw Exception::OpNotCalcSafe(traces, inner); + } + + callStackFrame frame(traces, inner->pstate()); + if (inner->operand() != ADD && inner->operand() != SUB) { + if (inner->operand() != MUL && inner->operand() != DIV) { + // Optimize to report span at operator + throw Exception::SassScriptException( + "This operation can't be used in a calculation.", + compiler, inner->pstate()); + } + } + auto rv = operateInternal(inner->pstate(), inner->operand(), + _visitCalculationExpression(inner->left(), inLegacySassFunction), + _visitCalculationExpression(inner->right(), inLegacySassFunction), + inLegacySassFunction, !inSupportsDeclaration); + return rv; + } + else { + const ListExpression* list = node->isaListExpression(); + if (list && !list->hasBrackets() && list->separator() == SASS_SPACE && list->size() > 1) { + sass::vector elements; + for (auto child : list->items()) { + elements.push_back(_visitCalculationExpression(child, inLegacySassFunction)); + } + + _checkAdjacentCalculationValues(elements, list); + + for (size_t i = 0; i < elements.size(); i++) { + if (elements[i]->isaCalcOperation()) { + if (list->items()[i]->isaParenthesizedExpression()) { + sass::string value("(" + elements[i]->inspect() + ")"); + elements[i] = SASS_MEMORY_NEW(String, + elements[i]->pstate(), std::move(value)); + } + } + } + + sass::string joined; + for (size_t i = 0; i < elements.size(); i++) { + if (i != 0) joined += " "; + joined += elements[i]->inspect(); + } + return SASS_MEMORY_NEW(String, + list->pstate(), std::move(joined)); + } + else { + callStackFrame frame(traces, node->pstate()); + throw Exception::SassScriptException( + "This expression can't be used in a calculation.", + traces, node->pstate()); + } + } + return node->accept(this); + } + + void Eval::_checkCalculationArguments(const sass::string& name, FunctionExpression* node, size_t maxArgs) + { + if (node->arguments()->positional().empty()) { + callStackFrame frame(traces, node->pstate()); + if (name == "sin" || name == "cos" || name == "tan") { + throw Exception::SassScriptException(logger, + node->pstate(), "Missing argument $angle."); + } + else if (maxArgs == 0) { + throw Exception::MustHaveArguments(logger, name); + } + else { + throw Exception::MissingArgument(logger, "number"); + } + } + size_t size = node->arguments()->positional().size(); + if (maxArgs != 0 && size > maxArgs) { + sass::sstream msg; + msg << "Only " << maxArgs << " "; + msg << pluralize("argument", maxArgs); + msg << " allowed, but " << size; + msg << pluralize(" was", size, " were"); + msg << " passed."; + callStackFrame frame(traces, node->pstate()); + throw Exception::SassScriptException( + logger, node->pstate(), msg.str()); + } + + } + + void Eval::_checkCalculationArguments(const sass::string& name, FunctionExpression* node) + { + if (name == "calc" || name == "sqrt" || name == "sin" || name == "cos" || + name == "tan" || name == "asin" || name == "acos" || name == "atan" || + name == "abs" || name == "exp" || name == "sign") + { + _checkCalculationArguments(name, node, 1); + } + else if (name == "min" || name == "max" || name == "hypot") + { + _checkCalculationArguments(name, node, 0); + } + else if (name == "pow" || name == "atan2" || + name == "log" || name == "mod" || name == "rem") + { + _checkCalculationArguments(name, node, 2); + } + else if (name == "round" || name == "clamp") + { + _checkCalculationArguments(name, node, 3); + } + } + + // Name is already in lowercase (original name can be found on function node) + Value* Eval::visitCalcuation(const sass::string& name, FunctionExpression* node, bool inLegacySassFunction) + { + + // std::cerr << "_visitCalcuation '" << name << "'\n"; + + // Evaluate original positionals and store in `arguments` + //ArgumentResults results(_evaluateArguments(node->arguments())); + // ValueVector args(results.positional()); + + if (!node->arguments()->named().empty()) { + callStackFrame frame(traces, node->pstate()); + throw Exception::SassScriptException(logger, node->pstate(), + "Keyword arguments can't be used with calculations."); + } + else if (node->arguments()->restArg() != nullptr) { + callStackFrame frame(traces, node->pstate()); + throw Exception::SassScriptException(logger, node->pstate(), + "Rest arguments can't be used with calculations."); + } + + _checkCalculationArguments(name, node); + + ExpressionVector args(node->arguments()->positional()); + ValueVector arguments(args.size()); // pre-init + std::transform(args.begin(), args.end(), arguments.begin(), + [&](ExpressionObj& arg) { return + + _visitCalculationExpression(arg, inLegacySassFunction); + // arg->accept(this); + }); + + if (inSupportsDeclaration) { + sass::vector inputs; + inputs.insert(inputs.end(), + arguments.begin(), + arguments.end()); + return new Calculation(node->pstate(), + node->name(), std::move(inputs)); + } + + // If we reach this point we have an error + // Mixin wasn't found and couldn't be executed + // This function trace is transparent (change ctx) + BackTrace trace(node->pstate(), name, true); + callStackFrame frame(traces, trace, false); + + ValueObj result; // we may get the same value as given in argument + + try { + + if (name == str_sqrt) { result = Sass::Calculation32::calc_sqrt(logger, node->pstate(), arguments); } + + else if (name == str_abs) { result = Sass::Calculation32::calc_abs(logger, node->pstate(), arguments); } + else if (name == str_exp) { result = Sass::Calculation32::calc_exp2(logger, node->pstate(), arguments); } + else if (name == str_sign) { result = Sass::Calculation32::calc_sign2(logger, node->pstate(), arguments); } // 2505 + + else if (name == str_sin) { result = Sass::Calculation32::calc_sin(logger, node->pstate(), arguments); } + else if (name == str_cos) { result = Sass::Calculation32::calc_cos(logger, node->pstate(), arguments); } + else if (name == str_tan) { result = Sass::Calculation32::calc_tan(logger, node->pstate(), arguments); } + else if (name == str_asin) { result = Sass::Calculation32::calc_asin(logger, node->pstate(), arguments); } + else if (name == str_acos) { result = Sass::Calculation32::calc_acos(logger, node->pstate(), arguments); } + else if (name == str_atan) { result = Sass::Calculation32::calc_atan(logger, node->pstate(), arguments); } + + else if (name == str_min) { result = Sass::Calculation32::calc_min(logger, node->pstate(), arguments); } + else if (name == str_max) { result = Sass::Calculation32::calc_max(logger, node->pstate(), arguments); } + + else if (name == str_pow) { result = Sass::Calculation32::calc_pow2(logger, node->pstate(), arguments); } + else if (name == str_mod) { result = Sass::Calculation32::calc_mod2(logger, node->pstate(), arguments); } + else if (name == str_rem) { result = Sass::Calculation32::calc_rem2(logger, node->pstate(), arguments); } + + else if (name == str_clamp) { result = Sass::Calculation32::calc_clamp(logger, node->pstate(), arguments); } + else if (name == str_hypot) { result = Sass::Calculation32::calc_hypot(logger, node->pstate(), arguments); } + else if (name == str_atan2) { result = Sass::Calculation32::calc_atan3(logger, node->pstate(), arguments); } + + // Without any implementation 2548 + else if (arguments.size() > 0) { + if (name == str_calc) { result = Sass::Calculation32::calc_fn(logger, arguments[0]); } // 2515 + else if (name == str_sqrt) { result = Sass::Calculation32::calc_sqrt(logger, node, arguments[0]); } // 2512 + + + // else if (name == str_exp) { result = Sass::Calculation32::calc_exp(logger, node, arguments[0]); } // 2505 + // else if (name == str_sign) { result = Sass::Calculation32::calc_sign(logger, node, arguments[0]); } // 2505 + + + else if (name == str_atan2) { + result = Sass::Calculation32::calc_atan2( + logger, node->pstate(), arguments[0], arguments.size() > 1 ? arguments[1] : nullptr); + } + + else if (name == str_log) { + result = Sass::Calculation32::calc_log( + logger, node, arguments[0], arguments.size() > 1 ? arguments[1] : nullptr); + } + + //else if (name == str_mod) { result = Sass::Calculation32::calc_mod( + // logger, node, arguments[0], arguments.size() > 1 ? arguments[1] : nullptr); } + else if (name == str_rem) { + result = Sass::Calculation32::calc_rem( + logger, node, arguments[0], arguments.size() > 1 ? arguments[1] : nullptr); + } + + else if (name == str_round) { result = Sass::Calculation32::calc_round(logger, node, arguments); } + + } + else { + if (name == str_sqrt) throw Exception::MissingArgument(logger, str_number); + if (name == str_sin) throw Exception::MissingArgument(logger, str_angle); + if (name == str_cos) throw Exception::MissingArgument(logger, str_angle); + if (name == str_tan) throw Exception::MissingArgument(logger, str_angle); + if (name == str_asin) throw Exception::MissingArgument(logger, str_number); + if (name == str_acos) throw Exception::MissingArgument(logger, str_number); + if (name == str_atan) throw Exception::MissingArgument(logger, str_number); + if (name == str_abs) throw Exception::MissingArgument(logger, str_number); + if (name == str_exp) throw Exception::MissingArgument(logger, str_number); + if (name == str_sign) throw Exception::MissingArgument(logger, str_number); + + if (name == str_min) throw Exception::MissingArgument(logger, str_number); + if (name == str_max) throw Exception::MissingArgument(logger, str_number); + + if (name == str_round) throw Exception::MissingArgument(logger, str_number); + if (name == str_mod) throw Exception::MissingArgument(logger, str_number); + if (name == str_rem) throw Exception::MissingArgument(logger, str_number); + + } + + } + catch (Exception::UnitMismatch ex) { + sass::vector foo; + for (auto qwe : arguments) { + foo.push_back(qwe); + } + _verifyCompatibleNumbers(foo, node->pstate()); + } + + //else if (name == str_asin) { result = Sass::Calculation32::calc_sin(logger, arguments[0]); } //todo + //else if (name == str_acos) { result = Sass::Calculation32::calc_cos(logger, arguments[0]); } //todo + //else if (name == str_atan) { result = Sass::Calculation32::calc_tan(logger, arguments[0]); } //todo + + // else if (name == str_abs) { result = Sass::Calculation32::calc_sqrt(logger, arguments[0]); } //todo + // else if (name == str_exp) { result = Sass::Calculation32::calc_sqrt(logger, arguments[0]); } //todo + // else if (name == str_sign) { result = Sass::Calculation32::calc_sqrt(logger, arguments[0]); } //todo + + + + + // Only garbage collect stuff no longer used + if (result.isNull()) return SASS_MEMORY_NEW(String, node->pstate(), "na"); + return result.detach(); + + /* + + + if (node.arguments.named.isNotEmpty) { + throw _exception( + "Keyword arguments can't be used with calculations.", node.span); + } else if (node.arguments.rest != null) { + throw _exception( + "Rest arguments can't be used with calculations.", node.span); + } + */ + /* + _checkCalculationArguments(node); + var arguments = [ + for (var argument in node.arguments.positional) + _visitCalculationExpression(argument, + inLegacySassFunction: inLegacySassFunction) + ]; + */ + /* + if (_inSupportsDeclaration) { + return SassCalculation.unsimplified(node.name, arguments); + } + */ + /* + + var oldCallableNode = _callableNode; + _callableNode = node; + + try { + return switch (node.name.toLowerCase()) { + "calc" => SassCalculation.calc(arguments[0]), + "sqrt" => SassCalculation.sqrt(arguments[0]), + "sin" => SassCalculation.sin(arguments[0]), + "cos" => SassCalculation.cos(arguments[0]), + "tan" => SassCalculation.tan(arguments[0]), + "asin" => SassCalculation.asin(arguments[0]), + "acos" => SassCalculation.acos(arguments[0]), + "atan" => SassCalculation.atan(arguments[0]), + "abs" => SassCalculation.abs(arguments[0]), + "exp" => SassCalculation.exp(arguments[0]), + "sign" => SassCalculation.sign(arguments[0]), + "min" => SassCalculation.min(arguments), + "max" => SassCalculation.max(arguments), + "hypot" => SassCalculation.hypot(arguments), + "pow" => + SassCalculation.pow(arguments[0], arguments.elementAtOrNull(1)), + "atan2" => + SassCalculation.atan2(arguments[0], arguments.elementAtOrNull(1)), + "log" => + SassCalculation.log(arguments[0], arguments.elementAtOrNull(1)), + "mod" => + SassCalculation.mod(arguments[0], arguments.elementAtOrNull(1)), + "rem" => + SassCalculation.rem(arguments[0], arguments.elementAtOrNull(1)), + "round" => SassCalculation.round(arguments[0], + arguments.elementAtOrNull(1), arguments.elementAtOrNull(2)), + "clamp" => SassCalculation.clamp(arguments[0], + arguments.elementAtOrNull(1), arguments.elementAtOrNull(2)), + _ => throw UnsupportedError('Unknown calculation name "${node.name}".') + }; + } on SassScriptException catch (error, stackTrace) { + // The simplification logic in the [SassCalculation] static methods will + // throw an error if the arguments aren't compatible, but we have access + // to the original spans so we can throw a more informative error. + if (error.message.contains("compatible")) { + _verifyCompatibleNumbers(arguments, node.arguments.positional); + } + throwWithTrace(_exception(error.message, node.span), error, stackTrace); + } finally { + _callableNode = oldCallableNode; + } + */ + } + + //*************************************************// + // Helper to Execute/include a mixin (for meta apply) + //*************************************************// + + Value* Eval::applyMixin( + const SourceSpan& pstate, const EnvKey& name, + Callable* callable, + CallableDeclaration* ctblk, + CallableArguments* arguments) + { + + //std::cerr << "ApplyMixin: " << callable->name() << "\n"; + // debug_ast(ctblk); + + // 99% of all mixins are user defined (expect `load-css`) + if (auto mixin = callable->isaUserDefinedCallable()) { + + // An include expression must reference a mixin rule + MixinRule* rule = mixin->declaration()->isaMixinRule(); + + // Sanity assertion + if (rule == nullptr) { + throw Exception::RuntimeException(traces, + "Include doesn't reference a mixin!"); + } + + // Create new mixin for content block + // Prepares the content block to be called later + // Content blocks of includes are like mixins themselves + UserDefinedCallableObj cmixin; + + // Check if a content block was passed to include + if (ctblk != nullptr) { + // Create a new temporary mixin + // Attach current content block to it in order + // for it to being restored when it is invoked. + cmixin = SASS_MEMORY_NEW(UserDefinedCallable, + pstate, name, ctblk, content); + // Check if invoked mixin accepts a content block + if (!rule->hasContent()) { + callStackFrame frame(logger, ctblk->pstate()); + throw Exception::RuntimeException(logger, + "Mixin doesn't accept a content block."); + } + } + + // Change lexical status (RAII) + // Influences e.g. `content-exists` + RAII_FLAG(inMixin, true); + + // Add a special backtrace for include invocation + callStackFrame frame(logger, BackTrace( + pstate, mixin->envkey().orig(), true)); + + // Overwrite current content block mixin with new one + // Even overwrite it if no new content block was given + RAII_PTR(UserDefinedCallable, content, cmixin); + + // Return value can be ignored, but memory must still be collected + return _runUserDefinedCallable(arguments, mixin, pstate); + + } + // This is currently only used for `load-css` mixin + else if (auto builtin = callable->isaBuiltInCallable()) { + + // An include expression must reference a mixin rule + // MixinRule* rule = mixin->declaration()->isaMixinRule(); + + // Create new mixin for content block + // Prepares the content block to be called later + // Content blocks of includes are like mixins themselves + UserDefinedCallableObj cmixin; + + // Check if a content block was passed to include + if (ctblk != nullptr) { + // Create a new temporary mixin + // Attach current content block to it in order + // for it to being restored when it is invoked. + cmixin = SASS_MEMORY_NEW(UserDefinedCallable, + pstate, name, ctblk, content); + + if (!builtin->acceptsContent()) { + callStackFrame frame2(logger, BackTrace( + pstate, cmixin->envkey().orig(), true)); + callStackFrame frame(logger, ctblk->pstate()); + throw Exception::RuntimeException(logger, + "Mixin doesn't accept a content block."); + } + // Check if invoked mixin accepts a content block + // if (!rule->has) { + // } + } + + // Change lexical status (RAII) + // Influences e.g. `content-exists` + RAII_FLAG(inMixin, true); + + // Overwrite current content block mixin with new one + // Even overwrite it if no new content block was given + RAII_PTR(UserDefinedCallable, content, cmixin); + + // Return value can be ignored, but memory must still be collected + return builtin->execute(*this, arguments, pstate); + + } + + throw Exception::RuntimeException(compiler, + "Mixin44 has no callable associated."); + + } + + //*************************************************// + // Execute/include a mixin (return value must be collected) + // Cache dynamic lookup results in `midx` member + //*************************************************// + Value* Eval::visitIncludeRule(IncludeRule* include) + { + + // Check if mixin expression was already resolved + if (!include->midx().isValid()) { + // Try to fetch the mixin by finding it by name + include->midx(compiler.varRoot.findMixIdx(include->name(), include->ns())); + } + + // Check if function expressions is resolved now + // If not the expression is a regular css function + if (include->midx().isValid()) { + // Check if mixin is already defined on the frame/scope + // Can fail if the mixin definition comes after the usage + if (Callable* callable = compiler.varRoot.getMixin(include->midx())) { + // callStackFrame frame(logger, include->pstate(), true); + ValueObj value = applyMixin(include->pstate(), include->name(), + callable, include->content(), include->arguments()); + return nullptr; + } + } + + // If we reach this point we have an error + // Mixin wasn't found and couldn't be executed + callStackFrame frame(traces, include->pstate()); + + // Check if function was requested from a module and if that module actually exists + if (include->ns().empty() || compiler.varRoot.stack.back()->hasNameSpace(include->ns())) { + throw Exception::RuntimeException(traces, "Undefined mixin."); + } + + // Otherwise the module simply wasn't imported + throw Exception::ModuleUnknown(traces, include->ns()); + + } + // EO visitIncludeRule + + //*************************************************// + // See visitContentRule and visitIncludeRule + //*************************************************// + Value* Eval::visitContentBlock(ContentBlock* rule) + { + throw std::runtime_error("Evaluation handles " + "@include and its content block together."); + } + + //*************************************************// + // Invoke the current block mixin (if available) + //*************************************************// + Value* Eval::visitContentRule(ContentRule* c) + { + // Check if no content block can be called + // This is no error by design, just ignore + if (content == nullptr) return nullptr; + + // Get local reference to current content block + UserDefinedCallable* current = content; + + // Reset lexical status (RAII) + // Influences e.g. `content-exists` + RAII_FLAG(inMixin, false); + + // Add a special backtrace for include invocation + callStackFrame frame(logger, BackTrace( + c->pstate(), Strings::contentRule)); + + // Reset lexical pointer for current content block to the + // content block where the now invoked mixin was seen. This + // allows the content calls to be "wrapped recursively". + // Note: could have been implemented with a regular stack. + RAII_PTR(UserDefinedCallable, content, current->content()); + + // Execute the callable and return value which must be collected + return _runUserDefinedCallable(c->arguments(), current, c->pstate()); + + } + // EO visitContentRule + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + //*************************************************// + //*************************************************// + void Eval::callExternalMessageOverloadFunction(Callable* fn, Value* message) + { + // We know that warn override function can only be external + SASS_ASSERT(fn->isaExternalCallable(), "Custom callable must be external"); + ExternalCallable* def = static_cast(fn); + SassFunctionLambda lambda = def->lambda(); + struct SassValue* c_args = sass_make_list(SASS_COMMA, false); + sass_list_push(c_args, Value::wrap(message)); + struct SassValue* c_val = lambda( + c_args, compiler.wrap(), def->cookie()); + sass_delete_value(c_args); + sass_delete_value(c_val); + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + Value* Eval::visitDebugRule(DebugRule* node) + { + ValueObj message = node->expression()->accept(this); + EnvRef fidx = compiler.varRoot.findFnIdx(Keys::debugRule, ""); + if (fidx.isValid()) { + CallableObj& fn = compiler.varRoot.getFunction(fidx); + callExternalMessageOverloadFunction(fn, message); + } + else { + logger.addDebug(message-> + inspect(compiler.precision, false), + node->pstate()); + } + return nullptr; + } + + Value* Eval::visitWarnRule(WarnRule* node) + { + ValueObj message = node->expression()->accept(this); + EnvRef fidx = compiler.varRoot.findFnIdx(Keys::warnRule, ""); + if (fidx.isValid()) { + CallableObj& fn = compiler.varRoot.getFunction(fidx); + callExternalMessageOverloadFunction(fn, message); + } + else { + sass::string result(message->toCss(false)); + callStackFrame frame(logger, BackTrace(node->pstate())); + logger.addWarning(result, Logger::WARN_RULE); + } + return nullptr; + } + + Value* Eval::visitErrorRule(ErrorRule* node) + { + ValueObj message = node->expression()->accept(this); + EnvRef fidx = compiler.varRoot.findFnIdx(Keys::errorRule, ""); + if (fidx.isValid()) { + + CallableObj& fn = compiler.varRoot.getFunction(fidx); + callExternalMessageOverloadFunction(fn, message); + } + else { + sass::string result(message->inspect()); + traces.push_back(BackTrace(node->pstate())); + throw Exception::RuntimeException(traces, result); + } + return nullptr; + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + Value* Eval::visitStyleRule(StyleRule* node) + { + + // Create a scope for lexical block variables + EnvScope scope(compiler.varRoot, node->idxs); + // Keyframe blocks have a specific syntax inside them + // Therefore style rules render a bit different inside them + if (inKeyframes) { + // Find the parent we should append to (bubble up) + auto chroot = current->bubbleThrough(true); + // Create a new keyframe parser from the evaluated interpolation + KeyframeSelectorParser parser(compiler, SASS_MEMORY_NEW(SourceItpl, + node->interpolation()->pstate(), + acceptInterpolation(node->interpolation(), true, true))); + // Invoke the keyframe parser and create a new CssKeyframeBlock + CssKeyframeBlockObj child = SASS_MEMORY_NEW(CssKeyframeBlock, node->pstate(), + chroot, SASS_MEMORY_NEW(CssStringList, node->pstate(), parser.parse())); + // Add child to our parent + chroot->addChildAt(child, false); + // addChildAt(chroot, child); + // Visit the remaining items at new child + acceptChildrenAt(child, node); + } + // Regular style rule + else if (node->interpolation()) { + // Check current importer context + Import* imp = compiler.import_stack.back(); + bool plainCss = imp->syntax == SASS_IMPORT_CSS; + // Evaluate the interpolation and try to parse a selector list + SelectorListObj slist = interpolationToSelector(node->interpolation(), plainCss); + // std::cerr << "GOTACH [" << slist->inspect() << "]\n"; + slist = slist->resolveParentSelectors(selector(), traces, !atRootExcludingStyleRule); + if (slist->inspect() == "a b") { + // std::cerr << "Still fails\n"; + } + // std::cerr << "VISIT [" << slist->inspect() << "]\n"; + // slist = slist->produce(); + // Append new selector list to the stack + RAII_SELECTOR(selectorStack, slist); + // The copy is needed for parent reference evaluation + // dart-sass stores it as `originalSelector` member + // RAII_SELECTOR(originalStack, slist->produce()); + RAII_SELECTOR(originalStack, slist->getExplicitParent() ? + slist.ptr() : SASS_MEMORY_COPY(slist)); // Avoid copy if possible + // Make the new selectors known for the extender + // If previous extend rules match this selector it will + // immediately do the extending, extend rules that occur + // later will apply the extending to the existing ones. + if (modules.size()) { + // just get start accordingly? + // if (slist->hasPlaceholder()) { + } + if (slist->hasPlaceholder()) { + for (size_t i = 0; i < modules.size() - 1; i += 1) { + if (modules[i]->extender == nullptr) continue; + // modules[i]->extender->addSelector(slist, mediaStack.back()); + } + } + if (extender2) { + // std::cerr << "Adding selector with media stack\n"; + extender2->addSelector(slist, mediaStack.back()); + } + // check if selector must be extendable by downstream extends + + // std::cerr << "ADD [" << slist->inspect() << "]\n"; + // Find the parent we should append to (bubble up) + auto chroot = current->bubbleThrough(true); + // Create a new style rule at the correct parent + CssStyleRuleObj child = SASS_MEMORY_NEW(CssStyleRule, + node->pstate(), chroot, slist); + // Add child to our parent + //debug_ast(child); + chroot->addChildAt(child, true); + // Register new child as style rule + RAII_PTR(CssStyleRule, readStyleRule, child); + // Reset specific flag (not in an at-rule) + RAII_FLAG(atRootExcludingStyleRule, false); + + // if (!rule.isInvisibleOtherThanBogusCombinators) { + for (auto complex : slist->elements()) { + // if (!complex->isBogusStrict()) continue; + //std::cerr << "Bogus detected!!!!!!!!!!\n"; + } + // } + + // debug_ast(node); + // Visit the remaining items at child + return acceptChildrenAt(child, node); + } + else { + std::cerr << "WHAT THE FUCK\n"; + } + // Consumed node + return nullptr; + } + // EO visitStyleRule + + CssRoot* Eval::acceptRoot(Root* root) + { + + Preloader preloader(*this, root); + preloader.process(); + root->isCompiled = true; + + RAII_PTR(ExtensionStore, extender2, root->extender); + + CssRootObj css = SASS_MEMORY_NEW(CssRoot, root->pstate()); + RAII_PTR(CssParentNode, current, css); + + RAII_MODULE(modules, root); + RAII_PTR(Root, modctx42, root); + RAII_PTR(Root, extctx33, root); + + ImportStackFrame iframe(compiler, root->import); + + for (const StatementObj& item : root->elements()) { + Value* child = item->accept(this); + if (child) delete child; + } + return css.detach(); + + } + + CssRoot* Eval::acceptRoot2(Root* root) + { + + Preloader preloader(*this, root); + preloader.process(); + root->isCompiled = true; + + return _combineCss(root); + } + + // Make non recursive later! + void Eval::_visitUpstreamModule(Root* current, sass::vector& sorted, std::set& seen) + { + //std::cerr << "Visit " << current->import->getImpPath() << "\n"; + if (current->idxs->isImport) return; + for (Root* upstream : current->upstream) { + if (upstream->idxs->isImport) continue; + if (seen.count(upstream->import->getAbsPath())) continue; + _visitUpstreamModule(upstream, sorted, seen); + //std::cerr << " ++ " << current->import->getImpPath() << "\n"; + seen.insert(upstream->import->getAbsPath()); + } + sorted.push_back(current); + } + + sass::vector Eval::_topologicalModules(Root* root) + { + // Construct a topological ordering using depth-first traversal, as in + // https://en.wikipedia.org/wiki/Topological_sorting#Depth-first_search. + std::set seen; + sass::vector sorted; + + // Probably more efficient to push and resort + _visitUpstreamModule(root, sorted, seen); + std::reverse(sorted.begin(), sorted.end()); + + return sorted; + } + + CssRoot* Eval::_combineCss(Root* root, bool clone) + { + + RAII_PTR(ExtensionStore, extender2, root->extender); + + CssRootObj css = SASS_MEMORY_NEW(CssRoot, root->pstate()); + RAII_PTR(CssParentNode, current, css); + + RAII_MODULE(modules, root); + RAII_PTR(Root, modctx42, root); + RAII_PTR(Root, extctx33, root); + + ImportStackFrame iframe(compiler, root->import); + + // debug_ast(css, "-- "); + + for (const StatementObj& item : root->elements()) { + Value* child = item->accept(this); + if (child) delete child; + } + + // debug_ast(css, ":: "); + + auto sorted = _topologicalModules(root); + + _extendModules(sorted); + + //std::cerr << "=========== Accept root2 - Combine CSS "; + + // for (auto asd : sorted) { std::cerr << asd->import->getImpPath() << ", "; } + //std::cerr << "\n"; + + return css.detach(); + + } + + sass::string SetToString(ExtSet& set) { + sass::string msg = "{"; + for (auto& item : set) { + msg += item->toString(); + msg += ", "; + } + return msg + "}"; + } + + sass::string MapToString(ExtSmplSelSet& set) { + sass::string msg = "{"; + for (auto& item : set) { + msg += item->inspect(); + msg += ", "; + } + return msg + "}"; + } + + void Eval::_extendModules(sass::vector sortedModules) + { + + // std::cerr << "!!!!!!!!!! Extend modules " << sortedModules.size() << "\n"; + + std::unordered_map> downstreamExtensionStores; + + /// Extensions that haven't yet been satisfied by some upstream module. This + /// adds extensions when they're defined but not satisfied, and removes them + /// when they're satisfied by any module. + ExtSet unsatisfiedExtensions; + + for (Root* module : sortedModules) { + + const sass::string& key(module->import->getAbsPath()); + + //std::cerr << "Wade through sorted " << key << "\n"; + + // Create a snapshot of the simple selectors currently in the + // [ExtensionStore] so that we don't consider an extension "satisfied" + // below because of a simple selector added by another (sibling) + // extension. + ExtSmplSelSet originalSelectors; + for (auto& sel : module->extender->selectors54) { + // std::cerr << "insert [" << sel.first->inspect() << "]\n"; + originalSelectors.insert(sel.first); + } + + module->extender->addNonOriginalSelectors( + originalSelectors, unsatisfiedExtensions); + + // std::cerr << "OriginalsIn: " << MapToString(originalSelectors) << "\n"; + + // std::cerr << "unsatisfiedExtensions " << SetToString(unsatisfiedExtensions) << "\n"; + + auto downStreamIt = downstreamExtensionStores.find(key); + if (downStreamIt != downstreamExtensionStores.end()) { + // std::cerr << "+++++ Add url to ext " << key << "\n"; + // std::cerr << downStreamIt->second.at(0)->toString() << "\n"; + // std::cerr << "------------ Add downstream to extender\n"; + module->extender->addExtensions(downStreamIt->second); + } + else { + // std::cerr << "Could not find " << key << "\n"; + } - sass::string result(unquote(message->to_sass())); - options().output_style = outstyle; - error(result, e->pstate(), traces); - return 0; - } + if (module->extender->extensionsBySimpleSelector.empty()) { + //std::cerr << "!!!!!!!!!! Module extender " << module->import->getAbsPath() << " is empty\n"; + //auto& qwe = downstreamExtensionStores[module->import->getAbsPath()]; + //if (qwe.size()) std::cerr << "And the other " << qwe[0]->isEmpty() << "\n"; + //if (qwe.size()) { + //ExtensionStore* a = module->extender; + //ExtensionStore* other = qwe[0]; + //std::cerr << a << " DOWN " << other << "\n"; + //} + continue; + } - Expression* Eval::operator()(DebugRule* d) - { - Sass_Output_Style outstyle = options().output_style; - options().output_style = NESTED; - ExpressionObj message = d->value()->perform(this); - Env* env = environment(); + for (auto& upstream : module->upstream) { + const sass::string& url(upstream->import->getAbsPath()); + //std::cerr << "+++++ register " << url << " at " << module->extender << "\n"; + //std::cerr << module->extender->toString() << "\n"; + downstreamExtensionStores[url].push_back(module->extender); + } - // try to use generic function - if (env->has("@debug[f]")) { + //std::cerr << "unsatisfiedExtensions before del " << SetToString(unsatisfiedExtensions) << "\n"; + //std::cerr << "Originals: " << MapToString(originalSelectors) << "\n"; + module->extender->delNonOriginalSelectors( + originalSelectors, unsatisfiedExtensions); + //std::cerr << "unsatisfiedExtensions after del " << SetToString(unsatisfiedExtensions) << "\n"; - // add call stack entry - callee_stack().push_back({ - "@debug", - d->pstate().getPath(), - d->pstate().getLine(), - d->pstate().getColumn(), - SASS_CALLEE_FUNCTION, - { env } - }); + } - Definition* def = Cast((*env)["@debug[f]"]); - // Block_Obj body = def->block(); - // Native_Function func = def->native_function(); - Sass_Function_Entry c_function = def->c_function(); - Sass_Function_Fn c_func = sass_function_get_function(c_function); - - AST2C ast2c; - union Sass_Value* c_args = sass_make_list(1, SASS_COMMA, false); - sass_list_set_value(c_args, 0, message->perform(&ast2c)); - union Sass_Value* c_val = c_func(c_args, c_function, compiler()); - options().output_style = outstyle; - callee_stack().pop_back(); - sass_delete_value(c_args); - sass_delete_value(c_val); - return 0; + //std::cerr << "Check unsatisfiedExtensions now\n"; + if (!unsatisfiedExtensions.empty()) { + ExtensionObj extension = *unsatisfiedExtensions.begin(); + throw Exception::UnsatisfiedExtend(traces, extension); } - sass::string result(unquote(message->to_sass())); - sass::string abs_path(Sass::File::rel2abs(d->pstate().getPath(), cwd(), cwd())); - sass::string rel_path(Sass::File::abs2rel(d->pstate().getPath(), cwd(), cwd())); - sass::string output_path(Sass::File::path_for_console(rel_path, abs_path, d->pstate().getPath())); - options().output_style = outstyle; - - std::cerr << output_path << ":" << d->pstate().getLine() << " DEBUG: " << result; - std::cerr << std::endl; - return 0; } - - Expression* Eval::operator()(List* l) + CssParentNode* Eval::hoistStyleRule(CssParentNode* node) { - // special case for unevaluated map - if (l->separator() == SASS_HASH) { - Map_Obj lm = SASS_MEMORY_NEW(Map, - l->pstate(), - l->length() / 2); - for (size_t i = 0, L = l->length(); i < L; i += 2) - { - ExpressionObj key = (*l)[i+0]->perform(this); - ExpressionObj val = (*l)[i+1]->perform(this); - // make sure the color key never displays its real name - key->is_delayed(true); // verified - *lm << std::make_pair(key, val); - } - if (lm->has_duplicate_key()) { - traces.push_back(Backtrace(l->pstate())); - throw Exception::DuplicateKeyError(traces, *lm, *l); - } - - lm->is_interpolant(l->is_interpolant()); - return lm->perform(this); + if (isInStyleRule()) { + auto outer = SASS_MEMORY_RESECT(readStyleRule); + node->addChildAt(outer, false); + return outer; + } + else { + return node; } - // check if we should expand it - if (l->is_expanded()) return l; - // regular case for unevaluated lists - List_Obj ll = SASS_MEMORY_NEW(List, - l->pstate(), - l->length(), - l->separator(), - l->is_arglist(), - l->is_bracketed()); - for (size_t i = 0, L = l->length(); i < L; ++i) { - ll->append((*l)[i]->perform(this)); - } - ll->is_interpolant(l->is_interpolant()); - ll->from_selector(l->from_selector()); - ll->is_expanded(true); - return ll.detach(); } - Expression* Eval::operator()(Map* m) + Value* Eval::visitSupportsRule(SupportsRule* node) { - if (m->is_expanded()) return m; + ValueObj condition = SASS_MEMORY_NEW( + String, node->condition()->pstate(), + _visitSupportsCondition(node->condition())); + EnvScope scoped(compiler.varRoot, node->idxs); + auto chroot = current->bubbleThrough(true); + CssSupportsRuleObj css = SASS_MEMORY_NEW(CssSupportsRule, + node->pstate(), chroot, condition); + chroot->addChildAt(css, false); + acceptChildrenAt( + hoistStyleRule(css), + node->elements()); + return nullptr; + } - // make sure we're not starting with duplicate keys. - // the duplicate key state will have been set in the parser phase. - if (m->has_duplicate_key()) { - traces.push_back(Backtrace(m->pstate())); - throw Exception::DuplicateKeyError(traces, *m, *m); - } + CssParentNode* Eval::_trimIncluded(CssParentVector& nodes) + { - Map_Obj mm = SASS_MEMORY_NEW(Map, - m->pstate(), - m->length()); - for (auto key : m->keys()) { - Expression* ex_key = key->perform(this); - Expression* ex_val = m->at(key); - if (ex_val == NULL) continue; - ex_val = ex_val->perform(this); - *mm << std::make_pair(ex_key, ex_val); - } + auto _root = getRoot(); + if (nodes.empty()) return _root; - // check the evaluated keys aren't duplicates. - if (mm->has_duplicate_key()) { - traces.push_back(Backtrace(m->pstate())); - throw Exception::DuplicateKeyError(traces, *mm, *m); + auto parent = current; + size_t innermostContiguous = sass::string::npos; + for (size_t i = 0; i < nodes.size(); i++) { + while (parent != nodes[i]) { + innermostContiguous = sass::string::npos; + parent = parent->parent(); + } + if (innermostContiguous == sass::string::npos) { + innermostContiguous = i; + } + parent = parent->parent(); } - mm->is_expanded(true); - return mm.detach(); + if (parent != _root) return _root; + auto& root = nodes[innermostContiguous]; + nodes.resize(innermostContiguous); + return root; + } - Expression* Eval::operator()(Binary_Expression* b_in) + Value* Eval::visitAtRootRule(AtRootRule* node) { + EnvScope scoped(compiler.varRoot, node->idxs); + InterpolationObj itpl = node->query(); + AtRootQueryObj query; - ExpressionObj lhs = b_in->left(); - ExpressionObj rhs = b_in->right(); - enum Sass_OP op_type = b_in->optype(); - - if (op_type == Sass_OP::AND) { - // LOCAL_FLAG(force, true); - lhs = lhs->perform(this); - if (!*lhs) return lhs.detach(); - return rhs->perform(this); + if (node->query()) { + query = AtRootQuery::parse( + interpolationToSource( + node->query(), true), + compiler); } - else if (op_type == Sass_OP::OR) { - // LOCAL_FLAG(force, true); - lhs = lhs->perform(this); - if (*lhs) return lhs.detach(); - return rhs->perform(this); + else { + query = AtRootQuery::defaultQuery( + SourceSpan{ node->pstate() }); } - // Evaluate variables as early o - while (Variable* l_v = Cast(lhs)) { - lhs = operator()(l_v); - } - while (Variable* r_v = Cast(rhs)) { - rhs = operator()(r_v); - } + RAII_FLAG(inKeyframes, false); + RAII_FLAG(inUnknownAtRule, false); + RAII_FLAG(atRootExcludingStyleRule, + query && query->excludesStyleRules()); - Binary_ExpressionObj b = b_in; + CssParentNode* parent = current; + CssParentNode* orgParent = current; + CssParentVector included; - // Evaluate sub-expressions early on - while (Binary_Expression* l_b = Cast(lhs)) { - if (!force && l_b->is_delayed()) break; - lhs = operator()(l_b); - } - while (Binary_Expression* r_b = Cast(rhs)) { - if (!force && r_b->is_delayed()) break; - rhs = operator()(r_b); + while (parent && parent->parent()) { + // is!CssStylesheet (is!CssRootNode) + if (!query->excludes(parent)) { + included.emplace_back(parent); + } + parent = parent->parent(); } + auto root = _trimIncluded(included); - // don't eval delayed expressions (the '/' when used as a separator) - if (!force && op_type == Sass_OP::DIV && b->is_delayed()) { - b->right(b->right()->perform(this)); - b->left(b->left()->perform(this)); - return b.detach(); + if (root == orgParent) { + acceptChildrenAt(root, node); } - - // specific types we know are final - // handle them early to avoid overhead - if (Number* l_n = Cast(lhs)) { - // lhs is number and rhs is number - if (Number* r_n = Cast(rhs)) { - try { - switch (op_type) { - case Sass_OP::EQ: return *l_n == *r_n ? bool_true : bool_false; - case Sass_OP::NEQ: return *l_n == *r_n ? bool_false : bool_true; - case Sass_OP::LT: return *l_n < *r_n ? bool_true : bool_false; - case Sass_OP::GTE: return *l_n < *r_n ? bool_false : bool_true; - case Sass_OP::LTE: return *l_n < *r_n || *l_n == *r_n ? bool_true : bool_false; - case Sass_OP::GT: return *l_n < *r_n || *l_n == *r_n ? bool_false : bool_true; - case Sass_OP::ADD: case Sass_OP::SUB: case Sass_OP::MUL: case Sass_OP::DIV: case Sass_OP::MOD: - return Operators::op_numbers(op_type, *l_n, *r_n, options(), b_in->pstate()); - default: break; - } - } - catch (Exception::OperationError& err) - { - traces.push_back(Backtrace(b_in->pstate())); - throw Exception::SassValueError(traces, b_in->pstate(), err); + else { + CssParentNode* innerCopy = included.empty() ? + nullptr : SASS_MEMORY_RESECT(included.front()); + // if (innerCopy) innerCopy->clear(); + CssParentNode* outerCopy = innerCopy; + auto it = included.begin(); + // Included is not empty + if (it != included.end()) { + if (++it != included.end()) { + auto copy = SASS_MEMORY_RESECT(*it); + copy->addChildAt(outerCopy, false); + outerCopy = copy; } } - // lhs is number and rhs is color - // Todo: allow to work with HSLA colors - else if (Color* r_col = Cast(rhs)) { - Color_RGBA_Obj r_c = r_col->toRGBA(); - try { - switch (op_type) { - case Sass_OP::EQ: return *l_n == *r_c ? bool_true : bool_false; - case Sass_OP::NEQ: return *l_n == *r_c ? bool_false : bool_true; - case Sass_OP::ADD: case Sass_OP::SUB: case Sass_OP::MUL: case Sass_OP::DIV: case Sass_OP::MOD: - return Operators::op_number_color(op_type, *l_n, *r_c, options(), b_in->pstate()); - default: break; - } - } - catch (Exception::OperationError& err) - { - traces.push_back(Backtrace(b_in->pstate())); - throw Exception::SassValueError(traces, b_in->pstate(), err); - } + + if (outerCopy != nullptr) { + root->addChildAt(outerCopy, false); } - } - else if (Color* l_col = Cast(lhs)) { - Color_RGBA_Obj l_c = l_col->toRGBA(); - // lhs is color and rhs is color - if (Color* r_col = Cast(rhs)) { - Color_RGBA_Obj r_c = r_col->toRGBA(); - try { - switch (op_type) { - case Sass_OP::EQ: return *l_c == *r_c ? bool_true : bool_false; - case Sass_OP::NEQ: return *l_c == *r_c ? bool_false : bool_true; - case Sass_OP::LT: return *l_c < *r_c ? bool_true : bool_false; - case Sass_OP::GTE: return *l_c < *r_c ? bool_false : bool_true; - case Sass_OP::LTE: return *l_c < *r_c || *l_c == *r_c ? bool_true : bool_false; - case Sass_OP::GT: return *l_c < *r_c || *l_c == *r_c ? bool_false : bool_true; - case Sass_OP::ADD: case Sass_OP::SUB: case Sass_OP::MUL: case Sass_OP::DIV: case Sass_OP::MOD: - return Operators::op_colors(op_type, *l_c, *r_c, options(), b_in->pstate()); - default: break; - } - } - catch (Exception::OperationError& err) - { - traces.push_back(Backtrace(b_in->pstate())); - throw Exception::SassValueError(traces, b_in->pstate(), err); - } + + auto newParent = innerCopy == nullptr ? root : innerCopy; + + RAII_FLAG(inKeyframes, inKeyframes); + RAII_FLAG(inUnknownAtRule, inUnknownAtRule); + RAII_FLAG(atRootExcludingStyleRule, atRootExcludingStyleRule); + CssMediaQueryVector oldQueries = mediaQueries; + + if (query->excludesStyleRules()) { + atRootExcludingStyleRule = true; + } + + if (query->excludesMedia()) { + mediaQueries.clear(); + } + + if (inKeyframes && query->excludesName("keyframes")) { + inKeyframes = false; } - // lhs is color and rhs is number - else if (Number* r_n = Cast(rhs)) { - try { - switch (op_type) { - case Sass_OP::EQ: return *l_c == *r_n ? bool_true : bool_false; - case Sass_OP::NEQ: return *l_c == *r_n ? bool_false : bool_true; - case Sass_OP::ADD: case Sass_OP::SUB: case Sass_OP::MUL: case Sass_OP::DIV: case Sass_OP::MOD: - return Operators::op_color_number(op_type, *l_c, *r_n, options(), b_in->pstate()); - default: break; + + if (inUnknownAtRule) { + bool hasAtRuleInIncluded = false; + for (auto& include : included) { + // A flag on parent could save 1% + if (include->isaCssAtRule()) { + hasAtRuleInIncluded = true; + break; } } - catch (Exception::OperationError& err) - { - traces.push_back(Backtrace(b_in->pstate())); - throw Exception::SassValueError(traces, b_in->pstate(), err); + if (!hasAtRuleInIncluded) { + inUnknownAtRule = false; } } - } - String_Schema_Obj ret_schema; + acceptChildrenAt(newParent, node); - // only the last item will be used to eval the binary expression - if (String_Schema* s_l = Cast(b->left())) { - if (!s_l->has_interpolant() && (!s_l->is_right_interpolant())) { - ret_schema = SASS_MEMORY_NEW(String_Schema, b->pstate()); - Binary_ExpressionObj bin_ex = SASS_MEMORY_NEW(Binary_Expression, b->pstate(), - b->op(), s_l->last(), b->right()); - bin_ex->is_delayed(b->left()->is_delayed() || b->right()->is_delayed()); // unverified - for (size_t i = 0; i < s_l->length() - 1; ++i) { - ret_schema->append(s_l->at(i)->perform(this)); - } - ret_schema->append(bin_ex->perform(this)); - return ret_schema->perform(this); - } - } - if (String_Schema* s_r = Cast(b->right())) { + mediaQueries = oldQueries; - if (!s_r->has_interpolant() && (!s_r->is_left_interpolant() || op_type == Sass_OP::DIV)) { - ret_schema = SASS_MEMORY_NEW(String_Schema, b->pstate()); - Binary_ExpressionObj bin_ex = SASS_MEMORY_NEW(Binary_Expression, b->pstate(), - b->op(), b->left(), s_r->first()); - bin_ex->is_delayed(b->left()->is_delayed() || b->right()->is_delayed()); // verified - ret_schema->append(bin_ex->perform(this)); - for (size_t i = 1; i < s_r->length(); ++i) { - ret_schema->append(s_r->at(i)->perform(this)); - } - return ret_schema->perform(this); - } } - // fully evaluate their values - if (op_type == Sass_OP::EQ || - op_type == Sass_OP::NEQ || - op_type == Sass_OP::GT || - op_type == Sass_OP::GTE || - op_type == Sass_OP::LT || - op_type == Sass_OP::LTE) - { - LOCAL_FLAG(force, true); - lhs->is_expanded(false); - lhs->set_delayed(false); - lhs = lhs->perform(this); - rhs->is_expanded(false); - rhs->set_delayed(false); - rhs = rhs->perform(this); - } - else { - lhs = lhs->perform(this); + return nullptr; + } + + Value* Eval::visitAtRule(AtRule* node) + { + CssStringObj name = interpolationToCssString(node->name(), true, false); + CssStringObj value = interpolationToCssString(node->value(), true, true); + + if (node->empty()) { + CssAtRuleObj css = SASS_MEMORY_NEW(CssAtRule, + node->pstate(), current, name, value, node->isChildless()); + current->addChildAt(css, false); + return nullptr; } - // not a logical connective, so go ahead and eval the rhs - rhs = rhs->perform(this); - AST_Node_Obj lu = lhs; - AST_Node_Obj ru = rhs; + EnvScope scoped(compiler.varRoot, node->idxs); - Expression::Type l_type; - Expression::Type r_type; + sass::string normalized(StringUtils::unvendor(name->text())); + bool isKeyframe = normalized == "keyframes"; + RAII_FLAG(inUnknownAtRule, !isKeyframe); + RAII_FLAG(inKeyframes, isKeyframe); - // Is one of the operands an interpolant? - String_Schema_Obj s1 = Cast(b->left()); - String_Schema_Obj s2 = Cast(b->right()); - Binary_ExpressionObj b1 = Cast(b->left()); - Binary_ExpressionObj b2 = Cast(b->right()); - bool schema_op = false; + auto pu = current->bubbleThrough(true); - bool force_delay = (s2 && s2->is_left_interpolant()) || - (s1 && s1->is_right_interpolant()) || - (b1 && b1->is_right_interpolant()) || - (b2 && b2->is_left_interpolant()); + // ModifiableCssKeyframeBlock + CssAtRuleObj css = SASS_MEMORY_NEW(CssAtRule, + node->pstate(), pu, name, value, node->isChildless()); - if ((s1 && s1->has_interpolants()) || (s2 && s2->has_interpolants()) || force_delay) - { - if (op_type == Sass_OP::DIV || op_type == Sass_OP::MUL || op_type == Sass_OP::MOD || op_type == Sass_OP::ADD || op_type == Sass_OP::SUB || - op_type == Sass_OP::EQ) { - // If possible upgrade LHS to a number (for number to string compare) - if (String_Constant* str = Cast(lhs)) { - sass::string value(str->value()); - const char* start = value.c_str(); - if (Prelexer::sequence < Prelexer::dimension, Prelexer::end_of_file >(start) != 0) { - lhs = Parser::lexed_dimension(b->pstate(), str->value()); - } - } - // If possible upgrade RHS to a number (for string to number compare) - if (String_Constant* str = Cast(rhs)) { - sass::string value(str->value()); - const char* start = value.c_str(); - if (Prelexer::sequence < Prelexer::dimension, Prelexer::number >(start) != 0) { - rhs = Parser::lexed_dimension(b->pstate(), str->value()); - } - } - } + // Adds new empty atRule to Root! + pu->addChildAt(css, false); - To_Value to_value(ctx); - ValueObj v_l = Cast(lhs->perform(&to_value)); - ValueObj v_r = Cast(rhs->perform(&to_value)); + auto oldParent = current; + current = css; - if (force_delay) { - sass::string str(""); - str += v_l->to_string(options()); - if (b->op().ws_before) str += " "; - str += b->separator(); - if (b->op().ws_after) str += " "; - str += v_r->to_string(options()); - String_Constant* val = SASS_MEMORY_NEW(String_Constant, b->pstate(), str); - val->is_interpolant(b->left()->has_interpolant()); - return val; - } - } + if (!(!atRootExcludingStyleRule && readStyleRule != nullptr) || inKeyframes || name->text() == "font-face") { - // see if it's a relational expression - try { - switch(op_type) { - case Sass_OP::EQ: return SASS_MEMORY_NEW(Boolean, b->pstate(), Operators::eq(lhs, rhs)); - case Sass_OP::NEQ: return SASS_MEMORY_NEW(Boolean, b->pstate(), Operators::neq(lhs, rhs)); - case Sass_OP::GT: return SASS_MEMORY_NEW(Boolean, b->pstate(), Operators::gt(lhs, rhs)); - case Sass_OP::GTE: return SASS_MEMORY_NEW(Boolean, b->pstate(), Operators::gte(lhs, rhs)); - case Sass_OP::LT: return SASS_MEMORY_NEW(Boolean, b->pstate(), Operators::lt(lhs, rhs)); - case Sass_OP::LTE: return SASS_MEMORY_NEW(Boolean, b->pstate(), Operators::lte(lhs, rhs)); - default: break; + for (const auto& child : node->elements()) { + ValueObj val = child->accept(this); } - } - catch (Exception::OperationError& err) - { - traces.push_back(Backtrace(b->pstate())); - throw Exception::SassValueError(traces, b->pstate(), err); - } - l_type = lhs->concrete_type(); - r_type = rhs->concrete_type(); - // ToDo: throw error in op functions - // ToDo: then catch and re-throw them - ExpressionObj rv; - try { - SourceSpan pstate(b->pstate()); - if (l_type == Expression::NUMBER && r_type == Expression::NUMBER) { - Number* l_n = Cast(lhs); - Number* r_n = Cast(rhs); - l_n->reduce(); r_n->reduce(); - rv = Operators::op_numbers(op_type, *l_n, *r_n, options(), pstate); - } - else if (l_type == Expression::NUMBER && r_type == Expression::COLOR) { - Number* l_n = Cast(lhs); - Color_RGBA_Obj r_c = Cast(rhs)->toRGBA(); - rv = Operators::op_number_color(op_type, *l_n, *r_c, options(), pstate); - } - else if (l_type == Expression::COLOR && r_type == Expression::NUMBER) { - Color_RGBA_Obj l_c = Cast(lhs)->toRGBA(); - Number* r_n = Cast(rhs); - rv = Operators::op_color_number(op_type, *l_c, *r_n, options(), pstate); - } - else if (l_type == Expression::COLOR && r_type == Expression::COLOR) { - Color_RGBA_Obj l_c = Cast(lhs)->toRGBA(); - Color_RGBA_Obj r_c = Cast(rhs)->toRGBA(); - rv = Operators::op_colors(op_type, *l_c, *r_c, options(), pstate); - } - else { - To_Value to_value(ctx); - // this will leak if perform does not return a value! - ValueObj v_l = Cast(lhs->perform(&to_value)); - ValueObj v_r = Cast(rhs->perform(&to_value)); - bool interpolant = b->is_right_interpolant() || - b->is_left_interpolant() || - b->is_interpolant(); - if (op_type == Sass_OP::SUB) interpolant = false; - // if (op_type == Sass_OP::DIV) interpolant = true; - // check for type violations - if (l_type == Expression::MAP || l_type == Expression::FUNCTION_VAL) { - traces.push_back(Backtrace(v_l->pstate())); - throw Exception::InvalidValue(traces, *v_l); - } - if (r_type == Expression::MAP || l_type == Expression::FUNCTION_VAL) { - traces.push_back(Backtrace(v_r->pstate())); - throw Exception::InvalidValue(traces, *v_r); - } - Value* ex = Operators::op_strings(b->op(), *v_l, *v_r, options(), pstate, !interpolant); // pass true to compress - if (String_Constant* str = Cast(ex)) - { - if (str->concrete_type() == Expression::STRING) - { - String_Constant* lstr = Cast(lhs); - String_Constant* rstr = Cast(rhs); - if (op_type != Sass_OP::SUB) { - if (String_Constant* org = lstr ? lstr : rstr) - { str->quote_mark(org->quote_mark()); } - } - } - } - ex->is_interpolant(b->is_interpolant()); - rv = ex; - } - } - catch (Exception::OperationError& err) - { - traces.push_back(Backtrace(b->pstate())); - // throw Exception::Base(b->pstate(), err.what()); - throw Exception::SassValueError(traces, b->pstate(), err); } + else { + + // If we're in a style rule, copy it into the at-rule so that + // declarations immediately inside it have somewhere to go. + // For example, "a {@foo {b: c}}" should produce "@foo {a {b: c}}". + CssStyleRule* qwe = SASS_MEMORY_RESECT(readStyleRule); + css->addChildAt(qwe, false); + acceptChildrenAt(qwe, node->elements()); - if (rv) { - if (schema_op) { - // XXX: this is never hit via spec tests - (*s2)[0] = rv; - rv = s2->perform(this); - } } + current = oldParent; - return rv.detach(); + return nullptr; } - Expression* Eval::operator()(Unary_Expression* u) + Value* Eval::visitMediaRule(MediaRule* node) { - ExpressionObj operand = u->operand()->perform(this); - if (u->optype() == Unary_Expression::NOT) { - Boolean* result = SASS_MEMORY_NEW(Boolean, u->pstate(), (bool)*operand); - result->value(!result->value()); - return result; - } - else if (Number_Obj nr = Cast(operand)) { - // negate value for minus unary expression - if (u->optype() == Unary_Expression::MINUS) { - Number_Obj cpy = SASS_MEMORY_COPY(nr); - cpy->value( - cpy->value() ); // negate value - return cpy.detach(); // return the copy - } - else if (u->optype() == Unary_Expression::SLASH) { - sass::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 output just +/-, - // but +/- null itself outputs the string - if (operand->concrete_type() == Expression::NULL_VAL && Cast(u->operand())) { - u->operand(SASS_MEMORY_NEW(String_Quoted, u->pstate(), "")); - } - // Never apply unary opertions on colors @see #2140 - else if (Color* color = Cast(operand)) { - // Use the color name if this was eval with one - if (color->disp().length() > 0) { - Unary_ExpressionObj cpy = SASS_MEMORY_COPY(u); - cpy->operand(SASS_MEMORY_NEW(String_Constant, operand->pstate(), color->disp())); - return SASS_MEMORY_NEW(String_Quoted, - cpy->pstate(), - cpy->inspect()); - } - } - else { - Unary_ExpressionObj cpy = SASS_MEMORY_COPY(u); - cpy->operand(operand); - return SASS_MEMORY_NEW(String_Quoted, - cpy->pstate(), - cpy->inspect()); - } - return SASS_MEMORY_NEW(String_Quoted, - u->pstate(), - u->inspect()); + ExpressionObj mq; + sass::string str_mq; + const SourceSpan& state = node->query() ? + node->query()->pstate() : node->pstate(); + EnvScope scoped(compiler.varRoot, node->idxs); + if (node->query()) { + str_mq = acceptInterpolation(node->query(), false); } - // unreachable - return u; - } - Expression* Eval::operator()(Function_Call* c) - { - if (traces.size() > Constants::MaxCallStack) { - // XXX: this is never hit via spec tests - sass::ostream stm; - stm << "Stack depth exceeded max of " << Constants::MaxCallStack; - error(stm.str(), c->pstate(), traces); - } - if (Cast(c->sname())) { - ExpressionObj evaluated_name = c->sname()->perform(this); - ExpressionObj evaluated_args = c->arguments()->perform(this); - sass::string str(evaluated_name->to_string()); - str += evaluated_args->to_string(); - return SASS_MEMORY_NEW(String_Constant, c->pstate(), str); - } + MediaQueryParser parser(compiler, SASS_MEMORY_NEW( + SourceItpl, state, std::move(str_mq))); + CssMediaQueryVector parsed(parser.parse()); - sass::string name(Util::normalize_underscores(c->name())); - sass::string full_name(name + "[f]"); + CssMediaQueryVector mergedQueries + (mergeMediaQueries(mediaQueries, parsed)); - // we make a clone here, need to implement that further - Arguments_Obj args = c->arguments(); + if (mergedQueries.empty()) { + if (!mediaQueries.empty()) { + return nullptr; + } + mergedQueries = parsed; + } - Env* env = environment(); - if (!env->has(full_name) || (!c->via_call() && Prelexer::re_special_fun(name.c_str()))) { - if (!env->has("*[f]")) { - for (Argument_Obj arg : args->elements()) { - if (List_Obj ls = Cast(arg->value())) { - if (ls->size() == 0) error("() isn't a valid CSS value.", c->pstate(), traces); - } - } - args = Cast(args->perform(this)); - Function_Call_Obj lit = SASS_MEMORY_NEW(Function_Call, - c->pstate(), - c->name(), - args); - if (args->has_named_arguments()) { - error("Plain CSS function " + c->name() + " doesn't support keyword arguments", c->pstate(), traces); - } - String_Quoted* str = SASS_MEMORY_NEW(String_Quoted, - c->pstate(), - lit->to_string(options())); - str->is_interpolant(c->is_interpolant()); - return str; - } else { - // call generic function - full_name = "*[f]"; - } - } - - // further delay for calls - if (full_name != "call[f]") { - args->set_delayed(false); // verified - } - if (full_name != "if[f]") { - args = Cast(args->perform(this)); - } - Definition* def = Cast((*env)[full_name]); - - if (c->func()) def = c->func()->definition(); - - if (def->is_overload_stub()) { - sass::ostream ss; - size_t L = args->length(); - // account for rest arguments - if (args->has_rest_argument() && args->length() > 0) { - // get the rest arguments list - List* rest = Cast(args->last()->value()); - // arguments before rest argument plus rest - if (rest) L += rest->length() - 1; - } - ss << full_name << L; - full_name = ss.str(); - sass::string resolved_name(full_name); - if (!env->has(resolved_name)) error("overloaded function `" + sass::string(c->name()) + "` given wrong number of arguments", c->pstate(), traces); - def = Cast((*env)[resolved_name]); - } - - ExpressionObj result = c; - Block_Obj body = def->block(); - Native_Function func = def->native_function(); - Sass_Function_Entry c_function = def->c_function(); - - if (c->is_css()) return result.detach(); - - Parameters_Obj params = def->parameters(); - Env fn_env(def->environment()); - env_stack().push_back(&fn_env); - - if (func || body) { - bind(sass::string("Function"), c->name(), params, args, &fn_env, this, traces); - sass::string msg(", in function `" + c->name() + "`"); - traces.push_back(Backtrace(c->pstate(), msg)); - callee_stack().push_back({ - c->name().c_str(), - c->pstate().getPath(), - c->pstate().getLine(), - c->pstate().getColumn(), - SASS_CALLEE_FUNCTION, - { env } - }); + // Create a new CSS only representation of the media rule + CssMediaRuleObj css = SASS_MEMORY_NEW(CssMediaRule, + node->pstate(), current, mergedQueries); + auto chroot = current->bubbleThrough(false); + // addChildAt(chroot, css); + chroot->addChildAt(css, false); - // eval the body if user-defined or special, invoke underlying CPP function if native - if (body /* && !Prelexer::re_special_fun(name.c_str()) */) { - result = body->perform(this); - } - else if (func) { - result = func(fn_env, *env, ctx, def->signature(), c->pstate(), traces, exp.getSelectorStack(), exp.originalStack); - } - if (!result) { - error(sass::string("Function ") + c->name() + " finished without @return", c->pstate(), traces); - } - callee_stack().pop_back(); - traces.pop_back(); - } - - // else if it's a user-defined c function - // convert call into C-API compatible form - else if (c_function) { - Sass_Function_Fn c_func = sass_function_get_function(c_function); - if (full_name == "*[f]") { - String_Quoted_Obj str = SASS_MEMORY_NEW(String_Quoted, c->pstate(), c->name()); - Arguments_Obj new_args = SASS_MEMORY_NEW(Arguments, c->pstate()); - new_args->append(SASS_MEMORY_NEW(Argument, c->pstate(), str)); - new_args->concat(args); - args = new_args; - } - - // populates env with default values for params - sass::string ff(c->name()); - bind(sass::string("Function"), c->name(), params, args, &fn_env, this, traces); - sass::string msg(", in function `" + c->name() + "`"); - traces.push_back(Backtrace(c->pstate(), msg)); - callee_stack().push_back({ - c->name().c_str(), - c->pstate().getPath(), - c->pstate().getLine(), - c->pstate().getColumn(), - SASS_CALLEE_C_FUNCTION, - { env } - }); + RAII_PTR(CssParentNode, current, css); + auto oldMediaQueries(std::move(mediaQueries)); + mediaQueries = mergedQueries; + mediaStack.emplace_back(css); - AST2C ast2c; - union Sass_Value* c_args = sass_make_list(params->length(), SASS_COMMA, false); - for(size_t i = 0; i < params->length(); i++) { - Parameter_Obj param = params->at(i); - sass::string key = param->name(); - AST_Node_Obj node = fn_env.get_local(key); - ExpressionObj arg = Cast(node); - sass_list_set_value(c_args, i, arg->perform(&ast2c)); - } - union Sass_Value* c_val = c_func(c_args, c_function, compiler()); - if (sass_value_get_tag(c_val) == SASS_ERROR) { - sass::string message("error in C function " + c->name() + ": " + sass_error_get_message(c_val)); - sass_delete_value(c_val); - sass_delete_value(c_args); - error(message, c->pstate(), traces); - } else if (sass_value_get_tag(c_val) == SASS_WARNING) { - sass::string message("warning in C function " + c->name() + ": " + sass_warning_get_message(c_val)); - sass_delete_value(c_val); - sass_delete_value(c_args); - error(message, c->pstate(), traces); - } - result = c2ast(c_val, traces, c->pstate()); - - callee_stack().pop_back(); - traces.pop_back(); - sass_delete_value(c_args); - if (c_val != c_args) - sass_delete_value(c_val); + if (isInStyleRule()) { + CssStyleRule* copy = SASS_MEMORY_RESECT(readStyleRule); + css->addChildAt(copy, false); + acceptChildrenAt(copy, node->elements()); + } + else { + for (auto& child : node->elements()) { + ValueObj rv = child->accept(this); + } } - // link back to function definition - // only do this for custom functions - if (result->pstate().getSrcId() == sass::string::npos) - result->pstate(c->pstate()); + mediaQueries = std::move(oldMediaQueries); - result = result->perform(this); - result->is_interpolant(c->is_interpolant()); - env_stack().pop_back(); - return result.detach(); - } + mediaStack.pop_back(); - Expression* Eval::operator()(Variable* v) - { - ExpressionObj value; - Env* env = environment(); - const sass::string& name(v->name()); - EnvResult rv(env->find(name)); - if (rv.found) value = static_cast(rv.it->second.ptr()); - else error("Undefined variable: \"" + v->name() + "\".", v->pstate(), traces); - if (Argument* arg = Cast(value)) value = arg->value(); - if (Number* nr = Cast(value)) nr->zero(true); // force flag - value->is_interpolant(v->is_interpolant()); - if (force) value->is_expanded(false); - value->set_delayed(false); // verified - value = value->perform(this); - if(!force) rv.it->second = value; - return value.detach(); + return nullptr; } - Expression* Eval::operator()(Color_RGBA* c) + Value* Eval::acceptChildren(const Vectorized& children) { - return c; + for (const auto& child : children) { + ValueObj val = child->accept(this); + if (val) return val.detach(); + } + return nullptr; } - Expression* Eval::operator()(Color_HSLA* c) + Value* Eval::acceptChildrenAt(CssParentNode* parent, + const Vectorized& children) { - return c; + RAII_PTR(CssParentNode, current, parent); + for (const auto& child : children) { + ValueObj val = child->accept(this); + if (val) return val.detach(); + } + return nullptr; } - Expression* Eval::operator()(Number* n) - { - return n; - } - Expression* Eval::operator()(Boolean* b) - { - return b; + /// Add parentheses if necessary. + /// + /// If [operator] is passed, it's the operator for the surrounding + /// [SupportsOperation], and is used to determine whether parentheses are + /// necessary if [condition] is also a [SupportsOperation]. + sass::string Eval::_parenthesize(SupportsCondition* condition) { + SupportsNegation* negation = condition->isaSupportsNegation(); + SupportsOperation* operation = condition->isaSupportsOperation(); + SupportsAnything* anything = condition->isaSupportsAnything(); + if (negation != nullptr || operation != nullptr || anything != nullptr) { + return "(" + _visitSupportsCondition(condition) + ")"; + } + else { + return _visitSupportsCondition(condition); + } } - void Eval::interpolation(Context& ctx, sass::string& res, ExpressionObj ex, bool into_quotes, bool was_itpl) { - - bool needs_closing_brace = false; + sass::string Eval::_parenthesize(SupportsCondition* condition, SupportsOperation::Operand operand) { + SupportsNegation* negation = condition->isaSupportsNegation(); + SupportsOperation* operation = condition->isaSupportsOperation(); + if (negation || (operation && operand != operation->operand())) { + return "(" + _visitSupportsCondition(condition) + ")"; + } + else { + return _visitSupportsCondition(condition); + } + } - if (Arguments* args = Cast(ex)) { - List* ll = SASS_MEMORY_NEW(List, args->pstate(), 0, SASS_COMMA); - for(auto arg : args->elements()) { - ll->append(arg->value()); - } - ll->is_interpolant(args->is_interpolant()); - needs_closing_brace = true; - res += "("; - ex = ll; + /// Evaluates [condition] and converts it to a plain CSS string, with + sass::string Eval::_visitSupportsCondition(SupportsCondition* condition) + { + if (SupportsOperation* operation = condition->isaSupportsOperation()) { + sass::string strm; + SupportsOperation::Operand operand = operation->operand(); + strm += _parenthesize(operation->left(), operand); + strm += (operand == SupportsOperation::AND ? " and " : " or "); + strm += _parenthesize(operation->right(), operand); + return strm; } - if (Number* nr = Cast(ex)) { - Number reduced(nr); - reduced.reduce(); - if (!reduced.is_valid_css_unit()) { - traces.push_back(Backtrace(nr->pstate())); - throw Exception::InvalidValue(traces, *nr); - } + else if (SupportsNegation* negation = condition->isaSupportsNegation()) { + return "not " + _parenthesize(negation->condition()); } - if (Argument* arg = Cast(ex)) { - ex = arg->value(); + else if (SupportsInterpolation* interpolation = condition->isaSupportsInterpolation()) { + return toCss(interpolation->value(), false); } - if (String_Quoted* sq = Cast(ex)) { - if (was_itpl) { - bool was_interpolant = ex->is_interpolant(); - ex = SASS_MEMORY_NEW(String_Constant, sq->pstate(), sq->value()); - ex->is_interpolant(was_interpolant); - } + else if (SupportsDeclaration* declaration = condition->isaSupportsDeclaration()) { + RAII_FLAG(inSupportsDeclaration, true); + return "(" + toCss(declaration->feature()) + ":" + + (declaration->isCustomProperty() ? "" : " ") + + toCss(declaration->value()) + ")"; } - - if (Cast(ex)) { return; } - - // parent selector needs another go - if (Cast(ex)) { - // XXX: this is never hit via spec tests - ex = ex->perform(this); + else if (SupportsFunction* function = condition->isaSupportsFunction()) { + return acceptInterpolation(function->name(), false) + + "(" + acceptInterpolation(function->args(), false) + ")"; + } + else if (SupportsAnything* anything = condition->isaSupportsAnything()) { + return "(" + acceptInterpolation(anything->contents(), false) + ")"; } + else { + return Strings::empty; + } + + } - if (List* l = Cast(ex)) { - List_Obj ll = SASS_MEMORY_NEW(List, l->pstate(), 0, l->separator()); - // this fixes an issue with bourbon sample, not really sure why - // if (l->size() && Cast((*l)[0])) { res += ""; } - for(ExpressionObj item : *l) { - item->is_interpolant(l->is_interpolant()); - sass::string rl(""); interpolation(ctx, rl, item, into_quotes, l->is_interpolant()); - bool is_null = Cast(item) != 0; // rl != "" - if (!is_null) ll->append(SASS_MEMORY_NEW(String_Quoted, item->pstate(), rl)); + /// Adds the values in [map] to [values]. + /// + /// Throws a [RuntimeException] associated with [nodeForSpan]'s source + /// span if any [map] keys aren't strings. + /// + /// If [convert] is passed, that's used to convert the map values to the value + /// type for [values]. Otherwise, the [Value]s are used as-is. + /// + /// This takes an [AstNode] rather than a [FileSpan] so it can avoid calling + /// [AstNode.span] if the span isn't required, since some nodes need to do + /// real work to manufacture a source span. + void Eval::_addRestValueMap(ValueFlatMap& values, Map* map, const SourceSpan& pstate) { + // convert ??= (value) = > value as T; + + for(const auto& kv : map->elements()) { + if (String* str = kv.first->isaString()) { + values.insert(std::make_pair(str->value(), kv.second)); } - // Check indicates that we probably should not get a list - // here. Normally single list items are already unwrapped. - if (l->size() > 1) { - // string_to_output would fail "#{'_\a' '_\a'}"; - sass::string str(ll->to_string(options())); - str = read_hex_escapes(str); // read escapes - newline_to_space(str); // replace directly - res += str; // append to result string - } else { - res += (ll->to_string(options())); + else { + callStackFrame frame(logger, pstate); + throw Exception::RuntimeException(logger, + "Variable keyword argument map must have string keys.\n" + + kv.first->inspect() + " is not a string in " + + map->inspect() + "."); } - ll->is_interpolant(l->is_interpolant()); } + } - // Value - // Function_Call - // Selector_List - // String_Quoted - // String_Constant - // Binary_Expression - else { - // ex = ex->perform(this); - if (into_quotes && ex->is_interpolant()) { - res += evacuate_escapes(ex ? ex->to_string(options()) : ""); - } else { - sass::string str(ex ? ex->to_string(options()) : ""); - if (into_quotes) str = read_hex_escapes(str); - res += str; // append to result string + /// Adds the values in [map] to [values]. + void Eval::_addRestExpressionMap(ExpressionFlatMap& values, Map* map, const SourceSpan& pstate) { + // convert ??= (value) = > value as T; + + for (const auto& kv : map->elements()) { + if (String* str = kv.first->isaString()) { + values.insert(std::make_pair(str->value(), SASS_MEMORY_NEW( + ValueExpression, map->pstate(), kv.second))); + } + else { + callStackFrame frame(logger, pstate); + throw Exception::RuntimeException(logger, + "Variable keyword argument map must have string keys.\n" + + kv.first->inspect() + " is not a string in " + + map->inspect() + "."); } } - - if (needs_closing_brace) res += ")"; - } - Expression* Eval::operator()(String_Schema* s) + + CssMediaQueryVector Eval::mergeMediaQueries( + const CssMediaQueryVector& lhs, + const CssMediaQueryVector& rhs) { - size_t L = s->length(); - bool into_quotes = false; - if (L > 1) { - if (!Cast((*s)[0]) && !Cast((*s)[L - 1])) { - if (String_Constant* l = Cast((*s)[0])) { - if (String_Constant* r = Cast((*s)[L - 1])) { - if (r->value().size() > 0) { - if (l->value()[0] == '"' && r->value()[r->value().size() - 1] == '"') into_quotes = true; - if (l->value()[0] == '\'' && r->value()[r->value().size() - 1] == '\'') into_quotes = true; - } + CssMediaQueryVector queries; + for (const CssMediaQueryObj& query1 : lhs) { + for (const CssMediaQueryObj& query2 : rhs) { + CssMediaQueryObj result(query1->merge(query2)); + if (result && !result->empty()) { + queries.emplace_back(result); } } - } } - bool was_quoted = false; - bool was_interpolant = false; - sass::string res(""); - for (size_t i = 0; i < L; ++i) { - bool is_quoted = Cast((*s)[i]) != NULL; - if (was_quoted && !(*s)[i]->is_interpolant() && !was_interpolant) { res += " "; } - else if (i > 0 && is_quoted && !(*s)[i]->is_interpolant() && !was_interpolant) { res += " "; } - ExpressionObj ex = (*s)[i]->perform(this); - interpolation(ctx, res, ex, into_quotes, ex->is_interpolant()); - was_quoted = Cast((*s)[i]) != NULL; - was_interpolant = (*s)[i]->is_interpolant(); + return queries; + } + Value* Eval::visitDeclaration(Declaration* node) + { + + if (!isInStyleRule() && !inUnknownAtRule && !inKeyframes) { + callStackFrame csf(logger, node->pstate()); + throw Exception::RuntimeException(traces, + "Declarations may only be used within style rules."); } - if (!s->is_interpolant()) { - if (s->length() > 1 && res == "") return SASS_MEMORY_NEW(Null, s->pstate()); - String_Constant_Obj str = SASS_MEMORY_NEW(String_Constant, s->pstate(), res, s->css()); - return str.detach(); + bool is_custom_property = node->is_custom_property(); + if (!declarationName.empty() && is_custom_property) { + callStackFrame csf(logger, node->pstate()); + throw Exception::RuntimeException(traces, + "Declarations whose names begin with \"--\" may not be nested."); } - // string schema seems to have a special unquoting behavior (also handles "nested" quotes) - String_Quoted_Obj str = SASS_MEMORY_NEW(String_Quoted, s->pstate(), res, 0, false, false, false, s->css()); - // if (s->is_interpolant()) str->quote_mark(0); - // String_Constant* str = SASS_MEMORY_NEW(String_Constant, s->pstate(), res); - if (str->quote_mark()) str->quote_mark('*'); - else if (!is_in_comment) str->value(string_to_output(str->value())); - str->is_interpolant(s->is_interpolant()); - return str.detach(); - } + CssStringObj name = interpolationToCssString(node->name(), true, false); - Expression* Eval::operator()(String_Constant* s) - { - return s; - } + if (!declarationName.empty()) { + name->text(declarationName + "-" + name->text()); + } - Expression* Eval::operator()(String_Quoted* s) - { - String_Quoted* str = SASS_MEMORY_NEW(String_Quoted, s->pstate(), ""); - str->value(s->value()); - str->quote_mark(s->quote_mark()); - str->is_interpolant(s->is_interpolant()); - return str; - } + ValueObj cssValue; + if (node->value()) { + cssValue = node->value()->accept(this); + } - Expression* Eval::operator()(SupportsOperation* c) - { - Expression* left = c->left()->perform(this); - Expression* right = c->right()->perform(this); - SupportsOperation* cc = SASS_MEMORY_NEW(SupportsOperation, - c->pstate(), - Cast(left), - Cast(right), - c->operand()); - return cc; - } + // The parent to add declarations too - Expression* Eval::operator()(SupportsNegation* c) - { - Expression* condition = c->condition()->perform(this); - SupportsNegation* cc = SASS_MEMORY_NEW(SupportsNegation, - c->pstate(), - Cast(condition)); - return cc; - } + // If the value is an empty list, preserve it, because converting it to CSS + // will throw an error that we want the user to see. + if (cssValue != nullptr && (!cssValue->isBlank() + || cssValue->lengthAsList() == 0)) { + current->append(SASS_MEMORY_NEW(CssDeclaration, + node->pstate(), name, cssValue, is_custom_property)); + } + else if (is_custom_property) { + callStackFrame frame(logger, node->value()->pstate()); + throw Exception::RuntimeException(logger, + "Custom property values may not be empty."); + } - Expression* Eval::operator()(SupportsDeclaration* c) - { - Expression* feature = c->feature()->perform(this); - Expression* value = c->value()->perform(this); - SupportsDeclaration* cc = SASS_MEMORY_NEW(SupportsDeclaration, - c->pstate(), - feature, - value); - return cc; + if (!node->empty()) { + LocalOption ll1(declarationName, name->text()); + for (Statement* child : node->elements()) { + ValueObj result = child->accept(this); + } + } + return nullptr; } - Expression* Eval::operator()(Supports_Interpolation* c) + Value* Eval::visitLoudComment(LoudComment* c) { - Expression* value = c->value()->perform(this); - Supports_Interpolation* cc = SASS_MEMORY_NEW(Supports_Interpolation, - c->pstate(), - value); - return cc; + if (inFunction) return nullptr; + sass::string text(acceptInterpolation(c->text(), false)); + bool preserve = text[2] == '!'; + current->append(SASS_MEMORY_NEW(CssComment, c->pstate(), text, preserve)); + return nullptr; } - Expression* Eval::operator()(At_Root_Query* e) + Value* Eval::visitIfRule(IfRule* i) { - ExpressionObj feature = e->feature(); - feature = (feature ? feature->perform(this) : 0); - ExpressionObj value = e->value(); - value = (value ? value->perform(this) : 0); - Expression* ee = SASS_MEMORY_NEW(At_Root_Query, - e->pstate(), - Cast(feature), - value); - return ee; + ValueObj rv; + // Has a condition? + if (i->predicate()) { + // Execute the condition statement + ValueObj condition = i->predicate()->accept(this); + // If true append all children of this clause + if (condition->isTruthy()) { + // Create local variable scope for children + EnvScope scoped(compiler.varRoot, i->idxs); + rv = acceptChildren(i); + } + else if (i->alternative()) { + // If condition is falsy, execute else blocks + rv = visitIfRule(i->alternative()); + } + } + else { + EnvScope scoped(compiler.varRoot, i->idxs); + rv = acceptChildren(i); + } + // Is probably nullptr!? + return rv.detach(); } - Media_Query* Eval::operator()(Media_Query* q) + // For does not create a new env scope + // But iteration vars are reset afterwards + Value* Eval::visitForRule(ForRule* f) { - String_Obj t = q->media_type(); - t = static_cast(t.isNull() ? 0 : t->perform(this)); - Media_Query_Obj qq = SASS_MEMORY_NEW(Media_Query, - q->pstate(), - t, - q->length(), - q->is_negated(), - q->is_restricted()); - for (size_t i = 0, L = q->length(); i < L; ++i) { - qq->append(static_cast((*q)[i]->perform(this))); + BackTrace trace(f->pstate(), Strings::forRule); + EnvScope scoped(compiler.varRoot, f->idxs); + ValueObj low = f->lower_bound()->accept(this); + ValueObj high = f->upper_bound()->accept(this); + NumberObj sass_start = low->assertNumber(logger, ""); + NumberObj sass_end = high->assertNumber(logger, ""); + // Support compatible unit types (e.g. cm to mm) + sass_end = sass_end->coerce(logger, sass_start); + // Can only use integer ranges + sass_start->assertInt(logger); + sass_end->assertInt(logger); + // check if units are valid for sequence + if (sass_start->unit() != sass_end->unit()) { + callStackFrame csf(logger, f->pstate()); + throw Exception::UnitMismatch( + logger, sass_start, sass_end); + } + double start = sass_start->value(); + double end = sass_end->value(); + // only create iterator once in this environment + ValueObj val; + if (start < end) { + if (f->is_inclusive()) ++end; + for (double i = start; i < end; ++i) { + NumberObj it = SASS_MEMORY_NEW(Number, + low->pstate(), i, sass_end->unit()); + compiler.varRoot.setVariable( + { f->idxs, 0 }, it.ptr(), false); + val = acceptChildren(f); + if (val) break; + } + } + else { + if (f->is_inclusive()) --end; + for (double i = start; i > end; --i) { + NumberObj it = SASS_MEMORY_NEW(Number, + low->pstate(), i, sass_end->unit()); + compiler.varRoot.setVariable( + { f->idxs, 0 }, it.ptr(), false); + val = acceptChildren(f); + if (val) break; + } } - return qq.detach(); + return val.detach(); } - Expression* Eval::operator()(Media_Query_Expression* e) + Value* Eval::visitExtendRule(ExtendRule* e) { - ExpressionObj feature = e->feature(); - feature = (feature ? feature->perform(this) : 0); - if (feature && Cast(feature)) { - feature = SASS_MEMORY_NEW(String_Quoted, - feature->pstate(), - Cast(feature)->value()); + // std::cerr << "+++ Adding " << selector()->inspect() << "\n"; + //std::cerr << "Visit extend\n"; + if (!isInStyleRule() /* || !declarationName.empty() */) { + callStackFrame csf(logger, e->pstate()); + throw Exception::RuntimeException(traces, + "@extend may only be used within style rules."); } - ExpressionObj value = e->value(); - value = (value ? value->perform(this) : 0); - if (value && Cast(value)) { - // XXX: this is never hit via spec tests - value = SASS_MEMORY_NEW(String_Quoted, - value->pstate(), - Cast(value)->value()); + + SelectorListObj slist = interpolationToSelector( + e->selector(), plainCss, current == nullptr); + + // std::cerr << "visit extend [" << slist->inspect() << "]\n"; + + if (slist) { + + for (const auto& complex : slist->elements()) { + + if (complex->size() != 1) { + callStackFrame csf(logger, complex->pstate()); + throw Exception::RuntimeException(traces, + "complex selectors may not be extended."); + } + + if (const CompoundSelector* compound = complex->first()->selector()) { + + if (compound->size() != 1) { + + sass::sstream sels; bool addComma = false; + sels << "compound selectors may no longer be extended.\nConsider `@extend "; + for (const auto& sel : compound->elements()) { + if (addComma) sels << ", "; + sels << sel->inspect(); + addComma = true; + } + sels << "` instead.\nSee https://sass-lang.com/d/extend-compound for details."; + #if SassRestrictCompoundExtending + callStackFrame csf(logger, compound->pstate()); + throw Exception::RuntimeException(traces, sels.str()); + #else + logger.addDeprecation(sels.str(), compound->pstate()); + #endif + + // Make this an error once deprecation is over + for (SimpleSelectorObj simple : compound->elements()) { +//UUU for (Root* mod : modctx->upstream) { +//UUU if (mod->extender) mod->extender->addExtension(selector(), simple, mediaStack.back(), e->is_optional()); +//UUU } + // Pass every selector we ever see to extender (to make them findable for extend) + if (extctx33) extctx33->addExtension(selector(), simple, mediaStack.back(), e, e->is_optional()); + else std::cerr << "No modctx\n"; + // if (extender2) extender2->addExtension(selector(), simple, mediaStack.back(), e, e->is_optional()); + } + + } + else { + // Add to all upstreams we saw sofar + // std::cerr << "+++ Adding " << compound->inspect() << "\n"; + if (extctx33) extctx33->addExtension(selector(), compound->first(), mediaStack.back(), e, e->is_optional()); + else std::cerr << "No modctx\n"; +// else if (extender2) extender2->addExtension(selector(), compound->first(), mediaStack.back(), e, e->is_optional()); + } + + } + else { + callStackFrame csf(logger, complex->pstate()); + throw Exception::RuntimeException(traces, + "complex selectors may not be extended."); + } + } } - return SASS_MEMORY_NEW(Media_Query_Expression, - e->pstate(), - feature, - value, - e->is_interpolated()); - } - Expression* Eval::operator()(Null* n) - { - return n; + return nullptr; } - Expression* Eval::operator()(Argument* a) + Value* Eval::visitEachRule(EachRule* e) { - ExpressionObj val = a->value()->perform(this); - bool is_rest_argument = a->is_rest_argument(); - bool is_keyword_argument = a->is_keyword_argument(); + const EnvRefs* vidx(e->idxs); + const sass::vector& variables(e->variables()); + EnvScope scoped(compiler.varRoot, e->idxs); + ValueObj expr = e->expressions()->accept(this); + if (MapObj map = expr->isaMap()) { + Map::ordered_map_type els(map->elements()); + for (const auto& kv : els) { + ValueObj key = kv.first; + ValueObj value = kv.second; + if (variables.size() == 1) { + List* variable = SASS_MEMORY_NEW(List, + map->pstate(), { key, value }, SASS_SPACE); + compiler.varRoot.setVariable({ vidx, 0 }, variable, false); + } + else { + value = withoutSlash(value); + compiler.varRoot.setVariable({ vidx, 0 }, key, false); + compiler.varRoot.setVariable({ vidx, 1 }, value, false); + } + ValueObj val = acceptChildren(e); + if (val) return val.detach(); + } + return nullptr; + } - if (a->is_rest_argument()) { - if (val->concrete_type() == Expression::MAP) { - is_rest_argument = false; - is_keyword_argument = true; + ListObj list; + if (List* slist = expr->isaList()) { + list = SASS_MEMORY_NEW(List, expr->pstate(), + slist->elements(), slist->separator()); + list->hasBrackets(slist->hasBrackets()); + } + else { + list = SASS_MEMORY_NEW(List, expr->pstate(), + { expr }, SASS_COMMA); + } + for (size_t i = 0, L = list->size(); i < L; ++i) { + Value* item = list->get(i); + // check if we got passed a list of args (investigate) + if (List* scalars = item->isaList()) { // Ex + if (variables.size() == 1) { + compiler.varRoot.setVariable({ vidx, 0 }, scalars, false); + } + else { + for (size_t j = 0, K = variables.size(); j < K; ++j) { + compiler.varRoot.setVariable({ vidx, (uint32_t)j }, + j < scalars->size() ? scalars->get(j) + : SASS_MEMORY_NEW(Null, expr->pstate()), false); + } + } } - else if(val->concrete_type() != Expression::LIST) { - List_Obj wrapper = SASS_MEMORY_NEW(List, - val->pstate(), - 0, - SASS_COMMA, - true); - wrapper->append(val); - val = wrapper; + else { + if (variables.size() > 0) { + compiler.varRoot.setVariable({ vidx, 0 }, item, false); + for (size_t j = 1, K = variables.size(); j < K; ++j) { + Value* res = SASS_MEMORY_NEW(Null, expr->pstate()); + compiler.varRoot.setVariable({ vidx, (uint32_t)j }, res, false); + } + } } + ValueObj val = acceptChildren(e); + if (val) return val.detach(); } - return SASS_MEMORY_NEW(Argument, - a->pstate(), - val, - a->name(), - is_rest_argument, - is_keyword_argument); + + return nullptr; } - Expression* Eval::operator()(Arguments* a) + Value* Eval::visitWhileRule(WhileRule* node) { - Arguments_Obj aa = SASS_MEMORY_NEW(Arguments, a->pstate()); - if (a->length() == 0) return aa.detach(); - for (size_t i = 0, L = a->length(); i < L; ++i) { - ExpressionObj rv = (*a)[i]->perform(this); - Argument* arg = Cast(rv); - if (!(arg->is_rest_argument() || arg->is_keyword_argument())) { - aa->append(arg); - } - } - - if (a->has_rest_argument()) { - ExpressionObj rest = a->get_rest_argument()->perform(this); - ExpressionObj splat = Cast(rest)->value()->perform(this); - Sass_Separator separator = SASS_COMMA; - List* ls = Cast(splat); - Map* ms = Cast(splat); + // First condition runs outside + EnvScope scoped(compiler.varRoot, node->idxs); + Expression* condition = node->condition(); + ValueObj result = condition->accept(this); - List_Obj arglist = SASS_MEMORY_NEW(List, - splat->pstate(), - 0, - ls ? ls->separator() : separator, - true); + // Evaluate the first run in outer scope + // All successive runs are from inner scope + if (result->isTruthy()) { - if (ls && ls->is_arglist()) { - arglist->concat(ls); - } else if (ms) { - aa->append(SASS_MEMORY_NEW(Argument, splat->pstate(), ms, "", false, true)); - } else if (ls) { - arglist->concat(ls); - } else { - arglist->append(splat); - } - if (arglist->length()) { - aa->append(SASS_MEMORY_NEW(Argument, splat->pstate(), arglist, "", true)); + while (true) { + result = acceptChildren(node); + if (result) { + return result.detach(); + } + result = condition->accept(this); + if (!result->isTruthy()) break; } + } - if (a->has_keyword_argument()) { - ExpressionObj rv = a->get_keyword_argument()->perform(this); - Argument* rvarg = Cast(rv); - ExpressionObj kwarg = rvarg->value()->perform(this); + return nullptr; - aa->append(SASS_MEMORY_NEW(Argument, kwarg->pstate(), kwarg, "", false, true)); - } - return aa.detach(); } - Expression* Eval::operator()(Comment* c) + Value* Eval::visitReturnRule(ReturnRule* rule) { - return 0; + ValueObj result(rule->value()->accept(this)); + return withoutSlash(result); } - SelectorList* Eval::operator()(Selector_Schema* s) + Value* Eval::visitSilentComment(SilentComment* c) { - LOCAL_FLAG(is_in_selector_schema, true); - // the parser will look for a brace to end the selector - ExpressionObj sel = s->contents()->perform(this); - sass::string result_str(sel->to_string(options())); - result_str = unquote(Util::rtrim(result_str)); - ItplFile* source = SASS_MEMORY_NEW(ItplFile, - result_str.c_str(), s->pstate()); - Parser p(source, ctx, traces); - - // If a schema contains a reference to parent it is already - // connected to it, so don't connect implicitly anymore - SelectorListObj parsed = p.parseSelectorList(true); - flag_is_in_selector_schema.reset(); - return parsed.detach(); + // current->append(c); + return nullptr; } - Expression* Eval::operator()(Parent_Reference* p) + + CssMediaQueryVector Eval::evalMediaQueries(Interpolation* itpl) { - if (SelectorListObj pr = exp.original()) { - return operator()(pr); - } else { - return SASS_MEMORY_NEW(Null, p->pstate()); - } + SourceDataObj synthetic = interpolationToSource(itpl, true); + MediaQueryParser parser(compiler, synthetic); + return parser.parse(); } - SimpleSelector* Eval::operator()(SimpleSelector* s) + void Eval::acceptStaticImport(StaticImport* rule) { - return s; + // Create new CssImport object + CssImportObj import = SASS_MEMORY_NEW(CssImport, rule->pstate(), + interpolationToCssString(rule->url(), false, false), + rule->modifiers() == nullptr ? nullptr : + interpolationToCssString(rule->modifiers(), false, false)); + import->outOfOrder(rule->outOfOrder()); + if (rule->modifiers()) { + // if (auto supports = rule->supports()->isaSupportsDeclaration()) { + // sass::string feature(toCss(supports->feature())); + // sass::string value(toCss(supports->value())); + // import->supports(SASS_MEMORY_NEW(CssString, + // rule->supports()->pstate(), + // // Should have a CssSupportsCondition? + // // Nope, spaces are even further down + // feature + ": " + value)); + // } + // else { + // import->supports(SASS_MEMORY_NEW(CssString, rule->supports()->pstate(), + // _visitSupportsCondition(rule->supports()))); + // } + // // Wrap the resulting condition into a `supports()` clause + // import->supports()->text("supports(" + import->supports()->text() + ")"); + // + //} + //if (rule->media()) { + // import->media(evalMediaQueries(rule->media())); + } + // append new css import to result + current->append(import.ptr()); + } - PseudoSelector* Eval::operator()(PseudoSelector* pseudo) + // Consume all imports in this rule + Value* Eval::visitImportRule(ImportRule* rule) { - // ToDo: should we eval selector? - return pseudo; - }; + for (const ImportBaseObj& import : rule->elements()) { + if (StaticImport* stimp = import->isaStaticImport()) { acceptStaticImport(stimp); } + else if (IncludeImport* stimp = import->isaIncludeImport()) { acceptIncludeImport(stimp); } + else throw std::runtime_error("undefined behavior"); + } + return nullptr; + } } diff --git a/src/eval.hpp b/src/eval.hpp index 2d8f3623e3..7bed7ee7f3 100644 --- a/src/eval.hpp +++ b/src/eval.hpp @@ -1,109 +1,449 @@ -#ifndef SASS_EVAL_H -#define SASS_EVAL_H +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#ifndef SASS_EVAL_HPP +#define SASS_EVAL_HPP -// sass.hpp must go before all system headers to get the -// __EXTENSIONS__ fix on Solaris. -#include "sass.hpp" -#include "ast.hpp" +// sass.hpp must go before all system headers +// to get the __EXTENSIONS__ fix on Solaris. +#include "capi_sass.hpp" -#include "context.hpp" -#include "listize.hpp" -#include "operation.hpp" -#include "environment.hpp" +// #include "context.hpp" +// #include "extender.hpp" +#include "ast_supports.hpp" +#include "ast_callables.hpp" namespace Sass { - class Expand; - class Context; - - class Eval : public Operation_CRTP { - - public: - Expand& exp; - Context& ctx; - Backtraces& traces; - Eval(Expand& exp); - ~Eval(); - - bool force; - bool is_in_comment; - bool is_in_selector_schema; - - Boolean_Obj bool_true; - Boolean_Obj bool_false; - - Env* environment(); - EnvStack& env_stack(); - const sass::string cwd(); - CalleeStack& callee_stack(); - struct Sass_Inspect_Options& options(); - struct Sass_Compiler* compiler(); - - // for evaluating function bodies - Expression* operator()(Block*); - Expression* operator()(Assignment*); - Expression* operator()(If*); - Expression* operator()(ForRule*); - Expression* operator()(EachRule*); - Expression* operator()(WhileRule*); - Expression* operator()(Return*); - Expression* operator()(WarningRule*); - Expression* operator()(ErrorRule*); - Expression* operator()(DebugRule*); - - Expression* operator()(List*); - Expression* operator()(Map*); - Expression* operator()(Binary_Expression*); - Expression* operator()(Unary_Expression*); - Expression* operator()(Function_Call*); - Expression* operator()(Variable*); - Expression* operator()(Number*); - Expression* operator()(Color_RGBA*); - Expression* operator()(Color_HSLA*); - Expression* operator()(Boolean*); - Expression* operator()(String_Schema*); - Expression* operator()(String_Quoted*); - Expression* operator()(String_Constant*); - Media_Query* operator()(Media_Query*); - Expression* operator()(Media_Query_Expression*); - Expression* operator()(At_Root_Query*); - Expression* operator()(SupportsOperation*); - Expression* operator()(SupportsNegation*); - Expression* operator()(SupportsDeclaration*); - Expression* operator()(Supports_Interpolation*); - Expression* operator()(Null*); - Expression* operator()(Argument*); - Expression* operator()(Arguments*); - Expression* operator()(Comment*); - - // these will return selectors - SelectorList* operator()(SelectorList*); - SelectorList* operator()(ComplexSelector*); - CompoundSelector* operator()(CompoundSelector*); - SelectorComponent* operator()(SelectorComponent*); - SimpleSelector* operator()(SimpleSelector* s); - PseudoSelector* operator()(PseudoSelector* s); - - // they don't have any specific implementation (yet) - IDSelector* operator()(IDSelector* s) { return s; }; - ClassSelector* operator()(ClassSelector* s) { return s; }; - TypeSelector* operator()(TypeSelector* s) { return s; }; - AttributeSelector* operator()(AttributeSelector* s) { return s; }; - PlaceholderSelector* operator()(PlaceholderSelector* s) { return s; }; - - // actual evaluated selectors - SelectorList* operator()(Selector_Schema*); - Expression* operator()(Parent_Reference*); - - // generic fallback - template - Expression* fallback(U x) - { return Cast(x); } + /*#####################################################################*/ + /*#####################################################################*/ + + class Eval : + public StatementVisitor, + public ExpressionVisitor { + + public: + + void exposeUseRule(UseRule* rule); + void exposeFwdRule(ForwardRule* rule); + void exposeImpRule(IncludeImport* rule); + + Value* doDivision(Value* left, Value* right, BinaryOpExpression* node, Logger& logger, SourceSpan pstate); + + Value* withoutSlash(ValueObj value); + + // Some references + Logger& logger; + Compiler& compiler; + BackTraces& traces; + + // Alias into context + Root*& modctx42; + + Root* extctx33 = nullptr; + + sass::vector modules; + + // Alias into context + WithConfig*& wconfig; + + // The extend handler + ExtensionStore* extender2; + + public: + + void _visitUpstreamModule(Root* upstream, sass::vector& sorted, std::set& seen); + CssRoot* _combineCss(Root* module, bool clone = false); + sass::vector _topologicalModules(Root* root); + void _extendModules(sass::vector sortedModules); + + + CssParentNode* current; + CssMediaVector mediaStack; + SelectorLists originalStack; + SelectorLists selectorStack; + + EnvRefs* pudding(EnvRefs* idxs, bool intoRoot, EnvRefs* modFrame); + + // A pointer to the slot where we will assign to + // Used to optimize self-assignment in functions + ValueObj* assigne = nullptr; + + // The name of the current declaration parent. Used for BEM- + // declaration blocks as in `div { prefix: { suffix: val; } }`; + sass::string declarationName; + + CssMediaQueryVector mediaQueries; + + // The style rule that defines the current parent selector, if any. + CssStyleRule* readStyleRule = nullptr; + + // Current content block + UserDefinedCallable* content = nullptr; + + // Whether we're working with plain css. + bool plainCss = false; + + // Whether we're currently executing a mixing. + bool inMixin = false; + + // Whether we're currently executing a function. + bool inFunction = false; + + // Whether we're currently building the output of an unknown at rule. + bool inUnknownAtRule = false; + + // Whether we're currently executing an import. + bool inImport = false; + + // Whether we're directly within an `@at-root` rule excluding style rules. + bool atRootExcludingStyleRule = false; + + // Whether we're currently building the output of a `@keyframes` rule. + bool inKeyframes = false; + + // Whether we're currently evaluating a [SupportsDeclaration]. + // When this is true, calculations will not be simplified. + bool inSupportsDeclaration = false; + + void insertModule(Module* module, bool clone = false); + void compileModule(Root* module); + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // ToDo: maybe create on demand for better pstate? + // ToDo: do some benchmarks to check implications! + BooleanObj bool_true; + BooleanObj bool_false; + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + public: + + // Value constructor + Eval(Compiler& compiler, + Logger& logger, + bool isCss = false); + + bool _operandAllowsSlash(const Expression* node) const; + + // Query if we are in a mixin + bool isInMixin() const { + return inMixin; + } + + // Query if use plain css + bool isPlainCss() const { + return plainCss; + } + + // Query if we have a content block + bool hasContentBlock() const { + return content != nullptr; + } + + // Check if there are any unsatisfied extends (will throw) +// bool checkForUnsatisfiedExtends(Extension& unsatisfied) const { +// return extender.checkForUnsatisfiedExtends(unsatisfied); +// } + + ExtSmplSelSet wasExtended; + + // Main entry point to evaluation + CssRoot* acceptRoot(Root* b); + CssRoot* acceptRoot2(Root* b); + + // Another entry point for the `call` sass-function + Value* acceptFunctionExpression(FunctionExpression* expression) { + return visitFunctionExpression(expression); + } + + // Converts the expression to css representation + sass::string toCss(Expression* expression, bool quote = true); + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // Call built-in function with no overloads + Value* execute( + BuiltInCallable* callable, + CallableArguments* arguments, + const SourceSpan& pstate); + + // Call built-in function with overloads + Value* execute( + BuiltInCallables* callable, + CallableArguments* arguments, + const SourceSpan& pstate); + + // Used for user functions and also by + // mixin includes and content includes. + Value* execute( + UserDefinedCallable* callable, + CallableArguments* arguments, + const SourceSpan& pstate); + + // Call external C-API function + Value* execute( + ExternalCallable* callable, + CallableArguments* arguments, + const SourceSpan& pstate); + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + private: + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + SelectorListObj& selector() { return selectorStack.back(); } + SelectorListObj& original() { return originalStack.back(); } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // Return root of current child + CssParentNode* getRoot() + { + auto parent = current; + while (parent->parent()) { + parent = parent->parent(); + } + return parent; + } + + // Check if we currently build + // the output of a style rule. + bool isInStyleRule() const { + return readStyleRule != nullptr && + !atRootExcludingStyleRule; + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// private: - void interpolation(Context& ctx, sass::string& res, ExpressionObj ex, bool into_quotes, bool was_itpl = false); - }; + // Fetch unevaluated positional argument (optionally by name) + // Will error if argument is missing or available both ways + // Note: only needed for lazy evaluation in if expressions + Expression* getArgument( + ExpressionVector& positional, + ExpressionFlatMap& named, + size_t idx, const EnvKey& name); + + // Fetch evaluated positional argument (optionally by name) + // Will error if argument is missing or available both ways + // Named arguments are consumed and removed from the hash + Value* getParameter( + ArgumentResults& evaled, + size_t idx, const Argument* arg); + + // Call built-in function with no overloads + Value* _runBuiltInCallable( + CallableArguments* arguments, + BuiltInCallable* callable, + const SourceSpan& pstate); + + // Call built-in function with overloads + Value* _runBuiltInCallables( + CallableArguments* arguments, + BuiltInCallables* callable, + const SourceSpan& pstate); + + // Helper for _runBuiltInCallable(s) + Value* _callBuiltInCallable( + ArgumentResults& evaluated, + const SassFnPair& function, + const SourceSpan& pstate); + public: + // Used for user functions and also by + // mixin includes and content includes. + Value* _runUserDefinedCallable( + CallableArguments* evaled, + UserDefinedCallable* callable, + const SourceSpan& pstate); + + // Call external C-API function + Value* _runExternalCallable( + CallableArguments* arguments, + ExternalCallable* callable, + const SourceSpan& pstate); + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + public: + ArgumentResults _evaluateArguments(CallableArguments* arguments); + void _addRestValueMap(ValueFlatMap& values, Map* map, const SourceSpan& nodeForSpan); + void _addRestExpressionMap(ExpressionFlatMap& values, Map* map, const SourceSpan& pstate); + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + sass::string acceptInterpolation(InterpolationObj interpolation, bool warnForColor, bool trim = false); + SourceData* interpolationToSource(InterpolationObj interpolation, bool warnForColor, bool trim = false, bool ws = true); + CssString* interpolationToCssString(InterpolationObj interpolation, bool warnForColor, bool trim = false); + SelectorListObj interpolationToSelector(Interpolation* interpolation, bool plainCss, bool allowParent = true); + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + void _evaluateMacroArguments(CallableArguments* arguments, + ExpressionVector& positional, + ExpressionFlatMap& named); + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + void acceptStaticImport(StaticImport* import); + void acceptIncludeImport(IncludeImport* import); + + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // Evaluate all children at the currently existing block context + Value* acceptChildren(const Vectorized& children); + + // Evaluate all children at a newly established current block context + Value* acceptChildrenAt(CssParentNode* parent, const Vectorized& children); + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + public: + void renderArgumentInvocation(sass::string& strm, CallableArguments* args); + + Value* _visitCalculationExpression(Expression* node, bool inLegacySassFunction); + + void _checkCalculationArguments(const sass::string& name, FunctionExpression* node, size_t maxArgs); + + void _checkCalculationArguments(const sass::string& name, FunctionExpression* node); + + Value* applyMixin( + const SourceSpan& pstate, const EnvKey& name, + Callable* callable, + CallableDeclaration* ctblk, + CallableArguments* arguments); + + private: + + Value* visitCalcuation(const sass::string& name, FunctionExpression* node, bool inLegacySassFunction); + + + protected: + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + Value* visitBinaryOpExpression(BinaryOpExpression*); + Value* visitBooleanExpression(BooleanExpression*); + Value* visitColorExpression(ColorExpression*); + Value* visitFunctionExpression(FunctionExpression*); + void _checkAdjacentCalculationValues(const ValueVector& elements, const ListExpression* node); + Value* visitIfExpression(IfExpression*); + Value* visitListExpression(ListExpression*); + Value* visitMapExpression(MapExpression*); + Value* visitNullExpression(NullExpression*); + Value* visitNumberExpression(NumberExpression*); + Value* visitItplFnExpression(ItplFnExpression*); + Value* visitParenthesizedExpression(ParenthesizedExpression*); + Value* visitSelectorExpression(SelectorExpression*); + Value* visitStringExpression(StringExpression*); + Value* visitSupportsExpression(SupportsExpression*); + Value* visitUnaryOpExpression(UnaryOpExpression*); + Value* visitValueExpression(ValueExpression*); + Value* visitVariableExpression(VariableExpression*); + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + Value* visitAtRootRule(AtRootRule* rule); + Value* visitAtRule(AtRule* rule); + Value* visitContentBlock(ContentBlock* rule); + Value* visitContentRule(ContentRule* rule); + Value* visitDebugRule(DebugRule* rule); + Value* visitDeclaration(Declaration* rule); + Value* visitEachRule(EachRule* rule); + Value* visitErrorRule(ErrorRule* rule); + Value* visitExtendRule(ExtendRule* rule); + Value* visitForRule(ForRule* rule); + Value* visitForwardRule(ForwardRule* rule); + Value* visitFunctionRule(FunctionRule* rule); + Value* visitIfRule(IfRule* rule); + Value* visitImportRule(ImportRule* rule); + Value* visitIncludeRule(IncludeRule* rule); + Value* visitLoudComment(LoudComment* rule); + Value* visitMediaRule(MediaRule* rule); + Value* visitMixinRule(MixinRule* rule); + Value* visitReturnRule(ReturnRule* rule); + Value* visitSilentComment(SilentComment* rule); + Value* visitStyleRule(StyleRule* rule); + // visitStylesheet + Value* visitSupportsRule(SupportsRule* rule); + Value* visitUseRule(UseRule* rule); + Value* visitAssignRule(AssignRule* rule); + Value* visitWarnRule(WarnRule* rule); + Value* visitWhileRule(WhileRule* rule); + + public: + Root* resolveIncludeImport(IncludeImport* rule); + + + // Backbone loader function + // Use by load-css directly + Root* loadModule( + const sass::string& prev, + const sass::string& url, + bool isImport = false); + + // Loading of parsed rules + Root* loadModRule(ModRule* rule); + + private: + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + void callExternalMessageOverloadFunction(Callable* fn, Value* message); + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + sass::string _visitSupportsCondition(SupportsCondition* condition); + sass::string _parenthesize(SupportsCondition* condition); + sass::string _parenthesize(SupportsCondition* condition, SupportsOperation::Operand operand); + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + CssParentNode* _trimIncluded(CssParentVector& nodes); + + CssParentNode* hoistStyleRule(CssParentNode* node); + + CssMediaQueryVector mergeMediaQueries(const CssMediaQueryVector& lhs, const CssMediaQueryVector& rhs); + + + + + + + + + + + + + + CssMediaQueryVector evalMediaQueries(Interpolation* itpl); + + + + void _verifyCompatibleNumbers(sass::vector args, const SourceSpan& pstate); + + Value* operateInternal(const SourceSpan& span, SassOperator op, AstNode* lhs, AstNode* rhs, bool inLegacySassFunction, bool simplify); + +}; } diff --git a/src/eval_selectors.cpp b/src/eval_selectors.cpp deleted file mode 100644 index 72035b5f56..0000000000 --- a/src/eval_selectors.cpp +++ /dev/null @@ -1,75 +0,0 @@ -// sass.hpp must go before all system headers to get the -// __EXTENSIONS__ fix on Solaris. -#include "sass.hpp" -#include "expand.hpp" -#include "eval.hpp" -#include "ast.hpp" - - -namespace Sass { - - SelectorList* Eval::operator()(SelectorList* s) - { - sass::vector rv; - SelectorListObj sl = SASS_MEMORY_NEW(SelectorList, s->pstate()); - for (size_t i = 0, iL = s->length(); i < iL; ++i) { - rv.push_back(operator()(s->get(i))); - } - - // we should actually permutate parent first - // but here we have permutated the selector first - size_t round = 0; - while (round != sass::string::npos) { - bool abort = true; - for (size_t i = 0, iL = rv.size(); i < iL; ++i) { - if (rv[i]->length() > round) { - sl->append((*rv[i])[round]); - abort = false; - } - } - if (abort) { - round = sass::string::npos; - } - else { - ++round; - } - - } - return sl.detach(); - } - - SelectorComponent* Eval::operator()(SelectorComponent* s) - { - return {}; - } - - SelectorList* Eval::operator()(ComplexSelector* s) - { - bool implicit_parent = !exp.old_at_root_without_rule; - if (is_in_selector_schema) exp.pushNullSelector(); - SelectorListObj other = s->resolve_parent_refs( - exp.getOriginalStack(), traces, implicit_parent); - if (is_in_selector_schema) exp.popNullSelector(); - - for (size_t i = 0; i < other->length(); i++) { - ComplexSelectorObj sel = other->at(i); - for (size_t n = 0; n < sel->length(); n++) { - if (CompoundSelectorObj comp = Cast(sel->at(n))) { - sel->at(n) = operator()(comp); - } - } - } - - return other.detach(); - } - - CompoundSelector* Eval::operator()(CompoundSelector* s) - { - for (size_t i = 0; i < s->length(); i++) { - SimpleSelector* ss = s->at(i); - // skip parents here (called via resolve_parent_refs) - s->at(i) = Cast(ss->perform(this)); - } - return s; - } -} diff --git a/src/exceptions.cpp b/src/exceptions.cpp new file mode 100644 index 0000000000..26324c87f1 --- /dev/null +++ b/src/exceptions.cpp @@ -0,0 +1,500 @@ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#include "exceptions.hpp" + +#include "ast_selectors.hpp" +#include "ast_values.hpp" +#include "extension.hpp" +#include + +namespace Sass { + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + StackTraces convertTraces(BackTraces traces) + { + // This will trigger StackTrace constructor + // Copies necessary stuff from BackTrace + return { traces.begin(), traces.end() }; + } + + StringVector getKeyVector( + const ValueFlatMap& names) + { + StringVector keys; + for (auto it : names) { + keys.push_back(it.first.orig()); + } + return keys; + } + + StringVector getKeyVector( + const ExpressionFlatMap& names) + { + StringVector keys; + for (auto it : names) { + keys.push_back(it.first.orig()); + } + return keys; + } + + sass::string pluralize( + const sass::string& singular, + size_t size, const sass::string& plural) + { + if (size == 1) return singular; + else if (!plural.empty()) return plural; + else return singular + "s"; + } + + sass::string toSentence( + const StringVector& names, + const sass::string& conjunction, + const sass::string& prefix, + const sass::string& postfix, + const uint8_t quote) + { + sass::string buffer; + size_t L = names.size(), i = 0; + auto it = names.begin(); + while (i < L) { + // add conjugation + if (i > 0) { + if (i < L - 1) { + buffer += ", "; + } + else { + buffer += " "; + buffer += conjunction; + buffer += " "; + } + } + buffer += prefix; + if (quote) buffer += quote; + buffer += *it; + if (quote) buffer += quote; + buffer += postfix; + it++; i++; + } + return buffer; + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + namespace Exception { + + Base::Base(sass::string msg, BackTraces traces) + : std::runtime_error(msg.c_str()), msg(msg), + traces(traces.begin(), traces.end()) + { } + + Base::Base(sass::string msg, BackTraces traces, SourceSpan pstate) + : Base(msg, traces) + { + Base::traces.push_back(pstate); + } + + ParserException::ParserException(BackTraces traces, sass::string msg) + : Base(msg, traces) + { } + + RuntimeException::RuntimeException( + BackTraces traces, sass::string msg) + : Base(msg, traces) + {} + + RuntimeException::RuntimeException( + sass::string msg, BackTraces traces, SourceSpan pstate) + : Base(msg, traces, pstate) + {} + + UnitMismatch::UnitMismatch(BackTraces traces, const Number& lhs, const Number& rhs) + : RuntimeException(traces, "Incompatible units " + + (rhs.isUnitless() ? "[unitless]" : rhs.unit()) + " and " + + (lhs.isUnitless() ? "[unitless]" : lhs.unit()) + ".") + { + this->traces.push_back(lhs.pstate()); + this->traces.push_back(rhs.pstate()); + } + + InvalidParent::InvalidParent(Selector* parent, BackTraces traces, Selector* selector) + : Base(def_msg, traces, selector->pstate()), parent(parent), selector(selector) + { + msg = "Selector \"" + parent->inspect() + "\"" + " can't be used as a parent in a compound selector."; + } + + + CustomImportError::CustomImportError(BackTraces traces, sass::string msg) + : Base(msg, traces) + { } + + CustomImportNotFound::CustomImportNotFound(BackTraces traces, sass::string file) + : RuntimeException(traces, def_msg) + { + msg = "Can't find stylesheet \"" + file + "\"."; + msg += "\nAs requested by custom importer."; + } + + CustomImportAmbigous::CustomImportAmbigous(BackTraces traces, sass::string file) + : RuntimeException(traces, def_msg) + { + msg = "CustomImportAmbigous \"" + file + "\"."; + msg += "\nAs requested by custom importer."; + } + + CustomImportLoadError::CustomImportLoadError(BackTraces traces, sass::string file) + : RuntimeException(traces, def_msg) + { + msg = "CustomImportLoadError \"" + file + "\"."; + msg += "\nAs requested by custom importer."; + } + + + RecursionLimitError::RecursionLimitError() + : Base(msg_recursion_limit, {}) {} + + EndlessExtendError::EndlessExtendError(BackTraces traces) + : Base(def_msg, traces) + { + msg = "Extend is creating an absurdly big selector, aborting!"; + } + + DuplicateKeyError::DuplicateKeyError(BackTraces traces, const Map& dup, const Value& org) + : Base(def_msg, traces), dup(dup), org(org) + { + // msg = "Duplicate key " + dup.get_duplicate_key()->inspect() + " in map (" + org.inspect() + ")."; + msg = "Duplicate key."; // dart-sass keeps it simple ... + } + + sass::string formatMixedParamGroups(const sass::string& first, const StringVector& others) + { + // RGB HWB + sass::string msg(first); + msg += " parameters may not be passed along with "; + msg += toSentence(others, Strings::_or_); + msg += " parameters."; + return msg; + } + + sass::string formatUnknownNamedArgument(const StringVector& names) + { + sass::string msg("No "); + msg += pluralize(Strings::argument, names.size()); + msg += " named "; + msg += toSentence(names, Strings::_or_, "$"); + msg += "."; + return msg; + } + + sass::string formatTooFewArguments(size_t given, size_t expected) { + sass::ostream msg; + msg << expected << " "; + msg << pluralize("argument", expected); + msg << " required, but only " << given << " "; + msg << pluralize("was", given, "were"); + msg << " passed."; + return msg.str(); + } + + sass::string formatTooFewArguments(const ExpressionFlatMap& given, const Sass::EnvKeySet& expected) { + StringVector superfluous; + for (auto pair : given) { + if (expected.count(pair.first) == 0) { + superfluous.emplace_back(pair.first.orig()); + } + } + return "No argument named " + + toSentence(superfluous, "or", "$") + "."; + } + + sass::string formatTooFewArguments(const ValueFlatMap& given, const Sass::EnvKeySet& expected) { + StringVector superfluous; + for (auto pair : given) { + if (expected.count(pair.first) == 0) { + superfluous.emplace_back(pair.first.orig()); + } + } + return "No argument named " + + toSentence(superfluous, "or", "$") + "."; + } + + sass::string formatTooFewArguments(const ValueFlatMap& superfluous) { + return "No argument named " + + toSentence(getKeyVector(superfluous), "or", "$") + "."; + } + + + TooFewArguments::TooFewArguments(BackTraces traces, size_t given, size_t expected) + : RuntimeException(traces, formatTooFewArguments(given, expected)) + {} + + TooFewArguments::TooFewArguments(BackTraces traces, const ExpressionFlatMap& given, const Sass::EnvKeySet& expected) + : RuntimeException(traces, formatTooFewArguments(given, expected)) + {} + + TooFewArguments::TooFewArguments(BackTraces traces, const ValueFlatMap& superflous) + : RuntimeException(traces, formatTooFewArguments(superflous)) + {} + + sass::string formatTooManyArguments(size_t given, size_t expected) { + sass::ostream msg; + msg << "Only " << expected << " "; + msg << pluralize("argument", expected); + msg << " allowed, but " << given << " "; + msg << pluralize("was", given, "were"); + msg << " passed."; + return msg.str(); + } + + sass::string formatTooManyArguments(const ExpressionFlatMap& given, const Sass::EnvKeySet& expected) { + StringVector superfluous; + for (auto pair : given) { + if (expected.count(pair.first) == 0) { + superfluous.emplace_back(pair.first.orig()); + } + } + return "No argument named " + + toSentence(superfluous, "or", "$") + "."; + } + + sass::string formatTooManyArguments(const ValueFlatMap& given, const Sass::EnvKeySet& expected) { + StringVector superfluous; + for (auto pair : given) { + if (expected.count(pair.first) == 0) { + superfluous.emplace_back(pair.first.orig()); + } + } + return "No argument named " + + toSentence(superfluous, "or", "$") + "."; + } + + sass::string formatTooManyArguments(const ValueFlatMap& superfluous) { + return "No argument named " + + toSentence(getKeyVector(superfluous), "or", "$") + "."; + } + + TooManyArguments::TooManyArguments(BackTraces traces, size_t given, size_t expected) + : RuntimeException(traces, formatTooManyArguments(given, expected)) + {} + + TooManyArguments::TooManyArguments(BackTraces traces, const ExpressionFlatMap& given, const Sass::EnvKeySet& expected) + : RuntimeException(traces, formatTooManyArguments(given, expected)) + {} + + TooManyArguments::TooManyArguments(BackTraces traces, const ValueFlatMap& superflous) + : RuntimeException(traces, formatTooManyArguments(superflous)) + {} + + NoAngleArgument::NoAngleArgument(BackTraces traces, const Value* value, const sass::string& name) + : RuntimeException(traces, "$" + name + ": Expected " + value->toString() + " to have an angle unit (deg, grad, rad, turn).") + {} + + MustHaveArguments::MustHaveArguments(BackTraces traces, const sass::string& name) + : RuntimeException(traces, name + "() must have at least one argument.") + {} + + + MissingArgument::MissingArgument(BackTraces traces, const EnvKey& name) + : RuntimeException(traces, "Missing argument $" + name.norm() + ".") + {} + + MissingArgument::MissingArgument(BackTraces traces, const sass::string & name) + : RuntimeException(traces, "Missing argument $" + name + ".") + {} + + ArgumentGivenTwice::ArgumentGivenTwice(BackTraces traces, const EnvKey& name) + : RuntimeException(traces, "Argument $" + name.norm() + " name was passed both by position and by name.") + {} + + UnknownNamedArgument::UnknownNamedArgument(BackTraces traces, ValueFlatMap names) + : RuntimeException(traces, formatUnknownNamedArgument(getKeyVector(names))) + { + } + + MixedParamGroups::MixedParamGroups(BackTraces traces, const sass::string& first, const StringVector others) + : RuntimeException(traces, formatMixedParamGroups(first, others)) + {} + + InvalidCssValue::InvalidCssValue(BackTraces traces, const Value& val) + : Base(val.inspect() + " isn't a valid CSS value.", traces, val.pstate()) + {} + + // Thrown when a parent selector is used without any parent + TopLevelParent::TopLevelParent(BackTraces traces, SourceSpan pstate) + : Base("Top-level selectors may not contain the parent selector \"&\".", traces, pstate) + {} + + // Thrown when a non-optional extend found nothing to extend + UnsatisfiedExtend::UnsatisfiedExtend(BackTraces traces, Extension* extension) + : Base("The target selector was not found.\n" + // Calling inspect to the placeholder is visible + "Use \"@extend " + extension->target->inspect() + + " !optional\" to avoid this error.", + traces, extension->pstate) + {} + + // Thrown when we extend across incompatible media contexts + ExtendAcrossMedia::ExtendAcrossMedia(BackTraces traces, const Extension* extension) + : Base("You may not @extend selectors across media queries.", traces) + {} + + ExtendAcrossMedia::ExtendAcrossMedia(BackTraces traces, const Extender* extender) + : Base("You may not @extend selectors across media queries.", traces) + {} + + // Thrown when we encounter some IO error (mainly when handling files) + IoError::IoError(BackTraces traces, const sass::string& msg, const sass::string& path) + : Base(msg + " <" + path + "> (" + strerror(errno) + ")", traces) + {} + + // Thrown when we find an unexpected UTF8 sequence + InvalidUnicode::InvalidUnicode(SourceSpan pstate, BackTraces traces) + : Base("Invalid UTF-8.", traces, pstate) + {} + + SassScriptException::SassScriptException( + BackTraces traces, SourceSpan pstate, + sass::string msg, sass::string name) : + Base(name.empty() ? msg : "$" + name + ": " + msg, traces) + {} + + SassScriptException::SassScriptException(sass::string msg, + BackTraces traces, SourceSpan pstate, sass::string name) : + Base(name.empty() ? msg : "$" + name + ": " + msg, traces) + {} + + + DeprecatedColorAdjustFn::DeprecatedColorAdjustFn( + Logger& logger, + const ValueVector& arguments, + sass::string name, + sass::string prefix, + sass::string secondarg) : + RuntimeException(logger, "") + { + msg += "The function " + name + "() isn't in the sass:color module.\n\n"; + msg += "Recommendation: color.adjust(" + arguments[0]->inspect() + + ", " + prefix + arguments[1]->inspect() + ")\n\n"; + msg += "More info: https://sass-lang.com/documentation/functions/color#" + name; + } + + ModuleUnknown::ModuleUnknown( + BackTraces traces, + sass::string name) : + RuntimeException(traces, + "There is no module with the" + " namespace \"" + name + "\".") + {} + + VariableUnknown::VariableUnknown( + BackTraces traces, + const EnvKey& name) : + RuntimeException(traces, + "Undefined variable.") + {} + + ModuleAlreadyKnown::ModuleAlreadyKnown( + BackTraces traces, + sass::string name) : + RuntimeException(traces, + "There's already a module with" + " namespace \"" + name + "\".") + {} + + TardyAtRule::TardyAtRule( + BackTraces traces, + sass::string name) : + RuntimeException(traces, + name + " rules must be written" + " before any other rules.") + {} + + InvalidSassIdentifier::InvalidSassIdentifier( + BackTraces traces, + sass::string name) : + RuntimeException(traces, + "Invalid Sass identifier" + " \"" + name + "\"") + {} + + UnknownImport::UnknownImport( + BackTraces traces) : + RuntimeException(traces, + "Can't find stylesheet to import.") + {} + + AmbiguousImports::AmbiguousImports(BackTraces traces, + sass::vector imports) : + RuntimeException(traces, + "It's not clear which file to import.") + { + msg += " Found:\n"; + for (size_t i = 0, L = imports.size(); i < L; ++i) + { msg += " " + imports[i].imp_path + "\n"; } + } + + + + IncompatibleCalcValue::IncompatibleCalcValue( + BackTraces traces, const AstNode& value, SourceSpan pstate) : + RuntimeException(traces, "Number " + value.toString() + + " isn't compatible with CSS calculations.") + { + this->traces.push_back(pstate); + } + + DuplicateKeyArgument::DuplicateKeyArgument( + BackTraces traces, const ValueFlatMap& superfluous) : + RuntimeException(traces, str_empty) + { + bool joiner = false; + msg += pluralize("Argument", superfluous.size()); + for (auto kv : superfluous) + { + if (joiner) msg = ","; + msg += " $" + kv.first.norm(); + joiner = true; + } + msg += pluralize(" was", superfluous.size(), " were"); + msg += " passed both by position and by name."; + } + + OpNotCalcSafe::OpNotCalcSafe(BackTraces traces, const BinaryOpExpression* op) : + RuntimeException(traces, "\"+\" and \"-\" must be surrounded by whitespace in calculations.") + { + this->traces.push_back(op->pstate()); + this->traces.push_back(op->opstate()); + } + + OpNotCalcSafe::OpNotCalcSafe(BackTraces traces, const Expression* op) : + RuntimeException(traces, "\"+\" and \"-\" must be surrounded by whitespace in calculations.") + { + this->traces.push_back(op->pstate()); + } + + MissingMathOp::MissingMathOp(BackTraces traces, const Expression* op) : + RuntimeException(traces, "Missing math operator.") + { + this->traces.push_back(op->pstate()); + } + + MissingMathOp::MissingMathOp(BackTraces traces, const Expression* lhs, const Expression* rhs) : + RuntimeException(traces, "Missing math operator.") + { + this->traces.push_back(SourceSpan::delta(lhs->pstate(), rhs->pstate())); + } + + InvalidDefaultNamespace::InvalidDefaultNamespace(BackTraces traces, sass::string name) : + RuntimeException(traces, str_empty) + { + msg = "The default namespace \"" + name + "\" is not a valid Sass identifier.\n"; + msg += "\nRecommendation: add an \"as\" clause to define an explicit namespace."; + } + +} + +} diff --git a/src/exceptions.hpp b/src/exceptions.hpp new file mode 100644 index 0000000000..c9103092cb --- /dev/null +++ b/src/exceptions.hpp @@ -0,0 +1,336 @@ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#ifndef SASS_ERROR_EXCEPTIONS_HPP +#define SASS_ERROR_EXCEPTIONS_HPP + +// sass.hpp must go before all system headers +// to get the __EXTENSIONS__ fix on Solaris. +#include "capi_sass.hpp" + +#include +#include +#include +#include "logger.hpp" +#include "units.hpp" +#include "file.hpp" +#include "backtrace.hpp" +#include "ast_fwd_decl.hpp" +#include "environment_cnt.hpp" + +namespace Sass { + + class Extender; + class BackTrace; + + sass::string toSentence( + const StringVector& names, + const sass::string& conjunction, + const sass::string& prefix = {}, + const sass::string& postfix = {}, + const uint8_t quote = 0); + + StringVector getKeyVector(const ValueFlatMap& names); + + sass::string pluralize(const sass::string& singular, size_t size, const sass::string& plural = ""); + + namespace Exception { + + const sass::string def_msg("Invalid sass detected"); + const sass::string def_op_null_msg("Invalid null operation"); + const sass::string def_nesting_limit("Code too deeply nested"); + + const sass::string msg_recursion_limit = + "Too deep recursion detected. This can be caused by too deep level nesting.\n" + "LibSass will abort here in order to avoid a possible stack overflow.\n"; + + class Base : public std::runtime_error { + public: + sass::string msg; + public: + StackTraces traces; + public: + Base(sass::string msg, BackTraces traces); + Base(sass::string msg, BackTraces traces, SourceSpan pstate); + virtual const char* what() const throw() { return msg.c_str(); } + // virtual ~Base() noexcept {}; + }; + + class ParserException : public Base { + public: + ParserException(BackTraces traces, sass::string msg); + }; + + class RuntimeException : public Base { + public: + RuntimeException(BackTraces traces, sass::string msg); + // RuntimeException(sass::string msg, BackTraces traces); + RuntimeException(sass::string msg, BackTraces traces, SourceSpan pstate); + virtual const char* what() const throw() { return msg.c_str(); } + }; + + class ModuleUnknown : public RuntimeException + { + public: + ModuleUnknown( + BackTraces traces, + sass::string name); + }; + + class VariableUnknown : public RuntimeException + { + public: + VariableUnknown( + BackTraces traces, + const EnvKey& name); + }; + + class ModuleAlreadyKnown : public RuntimeException + { + public: + ModuleAlreadyKnown( + BackTraces traces, + sass::string name); + }; + + class TardyAtRule : public RuntimeException + { + public: + TardyAtRule( + BackTraces traces, + sass::string name); + }; + + class InvalidSassIdentifier : public RuntimeException + { + public: + InvalidSassIdentifier( + BackTraces traces, + sass::string name); + }; + + class InvalidDefaultNamespace : public RuntimeException + { + public: + InvalidDefaultNamespace( + BackTraces traces, + sass::string name); + }; + + class UnknownImport : public RuntimeException + { + public: + UnknownImport( + BackTraces traces); + }; + + class AmbiguousImports : public RuntimeException + { + public: + AmbiguousImports(BackTraces traces, + sass::vector imports); + }; + + + class UnitMismatch : public RuntimeException + { + public: + UnitMismatch( + BackTraces traces, + const Number& lhs, + const Number& rhs); + }; + + class IncompatibleCalcValue : public RuntimeException + { + public: + IncompatibleCalcValue( + BackTraces traces, + const AstNode& lhs, + SourceSpan pstate); + }; + + + class DeprecatedColorAdjustFn : public RuntimeException + { + public: + DeprecatedColorAdjustFn( + Logger& logger, + const ValueVector& arguments, + sass::string name, + sass::string prefix, + sass::string secondarg = Strings::amount); + }; + + class InvalidParent : public Base { + protected: + Selector* parent; + Selector* selector; + public: + InvalidParent(Selector* parent, BackTraces traces, Selector* selector); + }; + + class InvalidUnicode : public Base { + public: + InvalidUnicode(SourceSpan pstate, BackTraces traces); + }; + + class CustomImportError : public Base { + public: + CustomImportError(BackTraces traces, sass::string msg); + }; + + class SassScriptException : public Base { + public: + SassScriptException( + BackTraces traces, + SourceSpan pstate, + sass::string msg, + sass::string name = ""); + + SassScriptException(sass::string msg, + BackTraces traces, SourceSpan pstate, + sass::string name = ""); + }; + + class CustomImportNotFound : public RuntimeException { + public: + CustomImportNotFound(BackTraces traces, sass::string file); + }; + + class CustomImportAmbigous : public RuntimeException { + public: + CustomImportAmbigous(BackTraces traces, sass::string file); + }; + + class CustomImportLoadError : public RuntimeException { + public: + CustomImportLoadError(BackTraces traces, sass::string file); + }; + + class RecursionLimitError : public Base { + public: + RecursionLimitError(); + }; + + class EndlessExtendError : public Base { + public: + EndlessExtendError(BackTraces traces); + }; + + class OpNotCalcSafe : public RuntimeException { + public: + OpNotCalcSafe(BackTraces traces, const BinaryOpExpression* op); + OpNotCalcSafe(BackTraces traces, const Expression* op); + }; + + class MissingMathOp : public RuntimeException { + public: + MissingMathOp(BackTraces traces, const Expression* op); + MissingMathOp(BackTraces traces, const Expression* lhs, const Expression* rhs); + }; + + class DuplicateKeyError : public Base { + protected: + const Map& dup; + const Value& org; + public: + DuplicateKeyError(BackTraces traces, + const Map& dup, const Value& org); + }; + + class TooFewArguments : public RuntimeException { + public: + TooFewArguments(BackTraces traces, size_t given, size_t expected); + TooFewArguments(BackTraces traces, const ExpressionFlatMap& given, const Sass::EnvKeySet& expected); + TooFewArguments(BackTraces traces, const ValueFlatMap& superfluous); + }; + + class DuplicateKeyArgument : public RuntimeException { + public: + DuplicateKeyArgument(BackTraces traces, const ValueFlatMap& superfluous); + }; + + class TooManyArguments : public RuntimeException { + public: + TooManyArguments(BackTraces traces, size_t given, size_t expected); + TooManyArguments(BackTraces traces, const ExpressionFlatMap& given, const Sass::EnvKeySet& expected); + TooManyArguments(BackTraces traces, const ValueFlatMap& superfluous); + }; + + class NoAngleArgument : public RuntimeException { + public: + NoAngleArgument(BackTraces traces, const Value* value, const sass::string& name); + }; + + class MissingArgument : public RuntimeException { + public: + MissingArgument(BackTraces traces, const EnvKey& name); + MissingArgument(BackTraces traces, const sass::string& name); + }; + + class MustHaveArguments : public RuntimeException { + public: + MustHaveArguments(BackTraces traces, const sass::string& name); + }; + + class ArgumentGivenTwice : public RuntimeException { + public: + ArgumentGivenTwice(BackTraces traces, const EnvKey& name); + }; + + class UnknownNamedArgument : public RuntimeException { + public: + UnknownNamedArgument(BackTraces traces, ValueFlatMap names); + }; + + class MixedParamGroups : public RuntimeException { + public: + MixedParamGroups(BackTraces traces, const sass::string& first, const StringVector seconds); + }; + + + class InvalidCssValue : public Base { + public: + InvalidCssValue(BackTraces traces, const Value& val); + }; + + + /* common virtual base class (has no pstate or trace) */ + class OperationError : public std::runtime_error { + protected: + sass::string msg; + public: + OperationError(sass::string msg = sass::string("Undefined operation")) + : std::runtime_error(msg.c_str()), msg(msg) + {}; + public: + virtual const char* what() const throw() { return msg.c_str(); } + }; + + class TopLevelParent : public Base { + public: + TopLevelParent(BackTraces traces, SourceSpan pstate); + }; + + class UnsatisfiedExtend : public Base { + public: + UnsatisfiedExtend(BackTraces traces, Extension* extension); + }; + + class ExtendAcrossMedia : public Base { + public: + ExtendAcrossMedia(BackTraces traces, const Extension* extension); + ExtendAcrossMedia(BackTraces traces, const Extender* extender); + }; + + class IoError : public Base { + public: + IoError(BackTraces traces, const sass::string& msg, const sass::string& path); + }; + + } + +} + +#endif diff --git a/src/expand.cpp b/src/expand.cpp deleted file mode 100644 index 89ed4fe132..0000000000 --- a/src/expand.cpp +++ /dev/null @@ -1,875 +0,0 @@ -// sass.hpp must go before all system headers to get the -// __EXTENSIONS__ fix on Solaris. -#include "sass.hpp" - -#include -#include - -#include "ast.hpp" -#include "expand.hpp" -#include "bind.hpp" -#include "eval.hpp" -#include "backtrace.hpp" -#include "context.hpp" -#include "parser.hpp" -#include "sass_functions.hpp" -#include "error_handling.hpp" - -namespace Sass { - - // simple endless recursion protection - const size_t maxRecursion = 500; - - Expand::Expand(Context& ctx, Env* env, SelectorStack* stack, SelectorStack* originals) - : ctx(ctx), - traces(ctx.traces), - eval(Eval(*this)), - recursions(0), - in_keyframes(false), - at_root_without_rule(false), - old_at_root_without_rule(false), - env_stack(), - block_stack(), - call_stack(), - selector_stack(), - originalStack(), - mediaStack() - { - env_stack.push_back(nullptr); - env_stack.push_back(env); - block_stack.push_back(nullptr); - call_stack.push_back({}); - if (stack == NULL) { pushToSelectorStack({}); } - else { - for (auto item : *stack) { - if (item.isNull()) pushToSelectorStack({}); - else pushToSelectorStack(item); - } - } - if (originals == NULL) { pushToOriginalStack({}); } - else { - for (auto item : *stack) { - if (item.isNull()) pushToOriginalStack({}); - else pushToOriginalStack(item); - } - } - mediaStack.push_back({}); - } - - Env* Expand::environment() - { - if (env_stack.size() > 0) - return env_stack.back(); - return 0; - } - - void Expand::pushNullSelector() - { - pushToSelectorStack({}); - pushToOriginalStack({}); - } - - void Expand::popNullSelector() - { - popFromOriginalStack(); - popFromSelectorStack(); - } - - SelectorStack Expand::getOriginalStack() - { - return originalStack; - } - - SelectorStack Expand::getSelectorStack() - { - return selector_stack; - } - - SelectorListObj& Expand::selector() - { - if (selector_stack.size() > 0) { - auto& sel = selector_stack.back(); - if (sel.isNull()) return sel; - return sel; - } - // Avoid the need to return copies - // We always want an empty first item - selector_stack.push_back({}); - return selector_stack.back();; - } - - SelectorListObj& Expand::original() - { - if (originalStack.size() > 0) { - auto& sel = originalStack.back(); - if (sel.isNull()) return sel; - return sel; - } - // Avoid the need to return copies - // We always want an empty first item - originalStack.push_back({}); - return originalStack.back(); - } - - SelectorListObj Expand::popFromSelectorStack() - { - SelectorListObj last = selector_stack.back(); - if (selector_stack.size() > 0) - selector_stack.pop_back(); - if (last.isNull()) return {}; - return last; - } - - void Expand::pushToSelectorStack(SelectorListObj selector) - { - selector_stack.push_back(selector); - } - - SelectorListObj Expand::popFromOriginalStack() - { - SelectorListObj last = originalStack.back(); - if (originalStack.size() > 0) - originalStack.pop_back(); - if (last.isNull()) return {}; - return last; - } - - void Expand::pushToOriginalStack(SelectorListObj selector) - { - originalStack.push_back(selector); - } - - // blocks create new variable scopes - Block* Expand::operator()(Block* b) - { - // create new local environment - // set the current env as parent - Env env(environment()); - // copy the block object (add items later) - Block_Obj bb = SASS_MEMORY_NEW(Block, - b->pstate(), - b->length(), - b->is_root()); - // setup block and env stack - this->block_stack.push_back(bb); - this->env_stack.push_back(&env); - // operate on block - // this may throw up! - this->append_block(b); - // revert block and env stack - this->block_stack.pop_back(); - this->env_stack.pop_back(); - // return copy - return bb.detach(); - } - - Statement* Expand::operator()(StyleRule* r) - { - LOCAL_FLAG(old_at_root_without_rule, at_root_without_rule); - - if (in_keyframes) { - Block* bb = operator()(r->block()); - Keyframe_Rule_Obj k = SASS_MEMORY_NEW(Keyframe_Rule, r->pstate(), bb); - if (r->schema()) { - pushNullSelector(); - k->name(eval(r->schema())); - popNullSelector(); - } - else if (r->selector()) { - if (SelectorListObj s = r->selector()) { - pushNullSelector(); - k->name(eval(s)); - popNullSelector(); - } - } - - return k.detach(); - } - - if (r->schema()) { - SelectorListObj sel = eval(r->schema()); - r->selector(sel); - for (auto complex : sel->elements()) { - // ToDo: maybe we can get rid of chroots? - complex->chroots(complex->has_real_parent_ref()); - } - - } - - // reset when leaving scope - LOCAL_FLAG(at_root_without_rule, false); - - SelectorListObj evaled = eval(r->selector()); - // do not connect parent again - Env env(environment()); - if (block_stack.back()->is_root()) { - env_stack.push_back(&env); - } - Block_Obj blk; - pushToSelectorStack(evaled); - // The copy is needed for parent reference evaluation - // dart-sass stores it as `originalSelector` member - pushToOriginalStack(SASS_MEMORY_COPY(evaled)); - ctx.extender.addSelector(evaled, mediaStack.back()); - if (r->block()) blk = operator()(r->block()); - popFromOriginalStack(); - popFromSelectorStack(); - StyleRule* rr = SASS_MEMORY_NEW(StyleRule, - r->pstate(), - evaled, - blk); - - if (block_stack.back()->is_root()) { - env_stack.pop_back(); - } - - rr->is_root(r->is_root()); - rr->tabs(r->tabs()); - - return rr; - } - - Statement* Expand::operator()(SupportsRule* f) - { - ExpressionObj condition = f->condition()->perform(&eval); - SupportsRuleObj ff = SASS_MEMORY_NEW(SupportsRule, - f->pstate(), - Cast(condition), - operator()(f->block())); - return ff.detach(); - } - - sass::vector Expand::mergeMediaQueries( - const sass::vector& lhs, - const sass::vector& rhs) - { - sass::vector queries; - for (CssMediaQuery_Obj query1 : lhs) { - for (CssMediaQuery_Obj query2 : rhs) { - CssMediaQuery_Obj result = query1->merge(query2); - if (result && !result->empty()) { - queries.push_back(result); - } - } - } - return queries; - } - - Statement* Expand::operator()(MediaRule* m) - { - ExpressionObj mq = eval(m->schema()); - sass::string str_mq(mq->to_css(ctx.c_options)); - ItplFile* source = SASS_MEMORY_NEW(ItplFile, - str_mq.c_str(), m->pstate()); - Parser parser(source, ctx, traces); - // Create a new CSS only representation of the media rule - CssMediaRuleObj css = SASS_MEMORY_NEW(CssMediaRule, m->pstate(), m->block()); - sass::vector parsed = parser.parseCssMediaQueries(); - if (mediaStack.size() && mediaStack.back()) { - auto& parent = mediaStack.back()->elements(); - css->concat(mergeMediaQueries(parent, parsed)); - } - else { - css->concat(parsed); - } - mediaStack.push_back(css); - css->block(operator()(m->block())); - mediaStack.pop_back(); - return css.detach(); - - } - - Statement* Expand::operator()(AtRootRule* a) - { - Block_Obj ab = a->block(); - ExpressionObj ae = a->expression(); - - if (ae) ae = ae->perform(&eval); - else ae = SASS_MEMORY_NEW(At_Root_Query, a->pstate()); - - LOCAL_FLAG(at_root_without_rule, Cast(ae)->exclude("rule")); - LOCAL_FLAG(in_keyframes, false); - - ; - - Block_Obj bb = ab ? operator()(ab) : NULL; - AtRootRuleObj aa = SASS_MEMORY_NEW(AtRootRule, - a->pstate(), - bb, - Cast(ae)); - return aa.detach(); - } - - Statement* Expand::operator()(AtRule* a) - { - LOCAL_FLAG(in_keyframes, a->is_keyframes()); - Block* ab = a->block(); - SelectorList* as = a->selector(); - Expression* av = a->value(); - pushNullSelector(); - if (av) av = av->perform(&eval); - if (as) as = eval(as); - popNullSelector(); - Block* bb = ab ? operator()(ab) : NULL; - AtRule* aa = SASS_MEMORY_NEW(AtRule, - a->pstate(), - a->keyword(), - as, - bb, - av); - return aa; - } - - Statement* Expand::operator()(Declaration* d) - { - Block_Obj ab = d->block(); - String_Obj old_p = d->property(); - ExpressionObj prop = old_p->perform(&eval); - String_Obj new_p = Cast(prop); - // we might get a color back - if (!new_p) { - sass::string str(prop->to_string(ctx.c_options)); - new_p = SASS_MEMORY_NEW(String_Constant, old_p->pstate(), str); - } - ExpressionObj value = d->value(); - if (value) value = value->perform(&eval); - Block_Obj bb = ab ? operator()(ab) : NULL; - if (!bb) { - if (!value || (value->is_invisible() && !d->is_important())) { - if (d->is_custom_property()) { - error("Custom property values may not be empty.", d->value()->pstate(), traces); - } else { - return nullptr; - } - } - } - Declaration* decl = SASS_MEMORY_NEW(Declaration, - d->pstate(), - new_p, - value, - d->is_important(), - d->is_custom_property(), - bb); - decl->tabs(d->tabs()); - return decl; - } - - Statement* Expand::operator()(Assignment* a) - { - Env* env = environment(); - const sass::string& var(a->variable()); - if (a->is_global()) { - if (!env->has_global(var)) { - deprecated( - "!global assignments won't be able to declare new variables in future versions.", - "Consider adding `" + var + ": null` at the top level.", - true, a->pstate()); - } - if (a->is_default()) { - if (env->has_global(var)) { - ExpressionObj e = Cast(env->get_global(var)); - if (!e || e->concrete_type() == Expression::NULL_VAL) { - env->set_global(var, a->value()->perform(&eval)); - } - } - else { - env->set_global(var, a->value()->perform(&eval)); - } - } - else { - env->set_global(var, a->value()->perform(&eval)); - } - } - else if (a->is_default()) { - if (env->has_lexical(var)) { - auto cur = env; - while (cur && cur->is_lexical()) { - if (cur->has_local(var)) { - if (AST_Node_Obj node = cur->get_local(var)) { - ExpressionObj e = Cast(node); - if (!e || e->concrete_type() == Expression::NULL_VAL) { - cur->set_local(var, a->value()->perform(&eval)); - } - } - else { - throw std::runtime_error("Env not in sync"); - } - return 0; - } - cur = cur->parent(); - } - throw std::runtime_error("Env not in sync"); - } - else if (env->has_global(var)) { - if (AST_Node_Obj node = env->get_global(var)) { - ExpressionObj e = Cast(node); - if (!e || e->concrete_type() == Expression::NULL_VAL) { - env->set_global(var, a->value()->perform(&eval)); - } - } - } - else if (env->is_lexical()) { - env->set_local(var, a->value()->perform(&eval)); - } - else { - env->set_local(var, a->value()->perform(&eval)); - } - } - else { - env->set_lexical(var, a->value()->perform(&eval)); - } - return 0; - } - - Statement* Expand::operator()(Import* imp) - { - Import_Obj result = SASS_MEMORY_NEW(Import, imp->pstate()); - if (imp->import_queries() && imp->import_queries()->size()) { - ExpressionObj ex = imp->import_queries()->perform(&eval); - result->import_queries(Cast(ex)); - } - for ( size_t i = 0, S = imp->urls().size(); i < S; ++i) { - result->urls().push_back(imp->urls()[i]->perform(&eval)); - } - // all resources have been dropped for Input_Stubs - // for ( size_t i = 0, S = imp->incs().size(); i < S; ++i) {} - return result.detach(); - } - - Statement* Expand::operator()(Import_Stub* i) - { - traces.push_back(Backtrace(i->pstate())); - // get parent node from call stack - AST_Node_Obj parent = call_stack.back(); - if (Cast(parent) == NULL) { - error("Import directives may not be used within control directives or mixins.", i->pstate(), traces); - } - // we don't seem to need that actually afterall - Sass_Import_Entry import = sass_make_import( - i->imp_path().c_str(), - i->abs_path().c_str(), - 0, 0 - ); - ctx.import_stack.push_back(import); - - Block_Obj trace_block = SASS_MEMORY_NEW(Block, i->pstate()); - Trace_Obj trace = SASS_MEMORY_NEW(Trace, i->pstate(), i->imp_path(), trace_block, 'i'); - block_stack.back()->append(trace); - block_stack.push_back(trace_block); - - const sass::string& abs_path(i->resource().abs_path); - append_block(ctx.sheets.at(abs_path).root); - sass_delete_import(ctx.import_stack.back()); - ctx.import_stack.pop_back(); - block_stack.pop_back(); - traces.pop_back(); - return 0; - } - - Statement* Expand::operator()(WarningRule* w) - { - // eval handles this too, because warnings may occur in functions - w->perform(&eval); - return 0; - } - - Statement* Expand::operator()(ErrorRule* e) - { - // eval handles this too, because errors may occur in functions - e->perform(&eval); - return 0; - } - - Statement* Expand::operator()(DebugRule* d) - { - // eval handles this too, because warnings may occur in functions - d->perform(&eval); - return 0; - } - - Statement* Expand::operator()(Comment* c) - { - if (ctx.output_style() == COMPRESSED) { - // comments should not be evaluated in compact - // https://github.com/sass/libsass/issues/2359 - if (!c->is_important()) return NULL; - } - eval.is_in_comment = true; - Comment* rv = SASS_MEMORY_NEW(Comment, c->pstate(), Cast(c->text()->perform(&eval)), c->is_important()); - eval.is_in_comment = false; - // TODO: eval the text, once we're parsing/storing it as a String_Schema - return rv; - } - - Statement* Expand::operator()(If* i) - { - Env env(environment(), true); - env_stack.push_back(&env); - call_stack.push_back(i); - ExpressionObj rv = i->predicate()->perform(&eval); - if (*rv) { - append_block(i->block()); - } - else { - Block* alt = i->alternative(); - if (alt) append_block(alt); - } - call_stack.pop_back(); - env_stack.pop_back(); - return 0; - } - - // For does not create a new env scope - // But iteration vars are reset afterwards - Statement* Expand::operator()(ForRule* f) - { - sass::string variable(f->variable()); - ExpressionObj low = f->lower_bound()->perform(&eval); - if (low->concrete_type() != Expression::NUMBER) { - traces.push_back(Backtrace(low->pstate())); - throw Exception::TypeMismatch(traces, *low, "integer"); - } - ExpressionObj high = f->upper_bound()->perform(&eval); - if (high->concrete_type() != Expression::NUMBER) { - traces.push_back(Backtrace(high->pstate())); - throw Exception::TypeMismatch(traces, *high, "integer"); - } - Number_Obj sass_start = Cast(low); - Number_Obj sass_end = Cast(high); - // check if units are valid for sequence - if (sass_start->unit() != sass_end->unit()) { - sass::ostream msg; msg << "Incompatible units: '" - << sass_start->unit() << "' and '" - << sass_end->unit() << "'."; - error(msg.str(), low->pstate(), traces); - } - double start = sass_start->value(); - double end = sass_end->value(); - // only create iterator once in this environment - Env env(environment(), true); - env_stack.push_back(&env); - call_stack.push_back(f); - Block* body = f->block(); - if (start < end) { - if (f->is_inclusive()) ++end; - for (double i = start; - i < end; - ++i) { - Number_Obj it = SASS_MEMORY_NEW(Number, low->pstate(), i, sass_end->unit()); - env.set_local(variable, it); - append_block(body); - } - } else { - if (f->is_inclusive()) --end; - for (double i = start; - i > end; - --i) { - Number_Obj it = SASS_MEMORY_NEW(Number, low->pstate(), i, sass_end->unit()); - env.set_local(variable, it); - append_block(body); - } - } - call_stack.pop_back(); - env_stack.pop_back(); - return 0; - } - - // Eval does not create a new env scope - // But iteration vars are reset afterwards - Statement* Expand::operator()(EachRule* e) - { - sass::vector variables(e->variables()); - ExpressionObj expr = e->list()->perform(&eval); - List_Obj list; - Map_Obj map; - if (expr->concrete_type() == Expression::MAP) { - map = Cast(expr); - } - else if (SelectorList * ls = Cast(expr)) { - ExpressionObj rv = Listize::perform(ls); - list = Cast(rv); - } - else if (expr->concrete_type() != Expression::LIST) { - list = SASS_MEMORY_NEW(List, expr->pstate(), 1, SASS_COMMA); - list->append(expr); - } - else { - list = Cast(expr); - } - // remember variables and then reset them - Env env(environment(), true); - env_stack.push_back(&env); - call_stack.push_back(e); - Block* body = e->block(); - - if (map) { - for (auto key : map->keys()) { - ExpressionObj k = key->perform(&eval); - ExpressionObj v = map->at(key)->perform(&eval); - - if (variables.size() == 1) { - List_Obj variable = SASS_MEMORY_NEW(List, map->pstate(), 2, SASS_SPACE); - variable->append(k); - variable->append(v); - env.set_local(variables[0], variable); - } else { - env.set_local(variables[0], k); - env.set_local(variables[1], v); - } - append_block(body); - } - } - else { - // bool arglist = list->is_arglist(); - if (list->length() == 1 && Cast(list)) { - list = Cast(list); - } - for (size_t i = 0, L = list->length(); i < L; ++i) { - ExpressionObj item = list->at(i); - // unwrap value if the expression is an argument - if (Argument_Obj arg = Cast(item)) item = arg->value(); - // check if we got passed a list of args (investigate) - if (List_Obj scalars = Cast(item)) { - if (variables.size() == 1) { - List_Obj var = scalars; - // if (arglist) var = (*scalars)[0]; - env.set_local(variables[0], var); - } else { - for (size_t j = 0, K = variables.size(); j < K; ++j) { - env.set_local(variables[j], j >= scalars->length() - ? SASS_MEMORY_NEW(Null, expr->pstate()) - : (*scalars)[j]->perform(&eval)); - } - } - } else { - if (variables.size() > 0) { - env.set_local(variables.at(0), item); - for (size_t j = 1, K = variables.size(); j < K; ++j) { - ExpressionObj res = SASS_MEMORY_NEW(Null, expr->pstate()); - env.set_local(variables[j], res); - } - } - } - append_block(body); - } - } - call_stack.pop_back(); - env_stack.pop_back(); - return 0; - } - - Statement* Expand::operator()(WhileRule* w) - { - ExpressionObj pred = w->predicate(); - Block* body = w->block(); - Env env(environment(), true); - env_stack.push_back(&env); - call_stack.push_back(w); - ExpressionObj cond = pred->perform(&eval); - while (!cond->is_false()) { - append_block(body); - cond = pred->perform(&eval); - } - call_stack.pop_back(); - env_stack.pop_back(); - return 0; - } - - Statement* Expand::operator()(Return* r) - { - error("@return may only be used within a function", r->pstate(), traces); - return 0; - } - - Statement* Expand::operator()(ExtendRule* e) - { - - // evaluate schema first - if (e->schema()) { - e->selector(eval(e->schema())); - e->isOptional(e->selector()->is_optional()); - } - // evaluate the selector - e->selector(eval(e->selector())); - - if (e->selector()) { - - for (auto complex : e->selector()->elements()) { - - if (complex->length() != 1) { - error("complex selectors may not be extended.", complex->pstate(), traces); - } - - if (const CompoundSelector* compound = complex->first()->getCompound()) { - - if (compound->length() != 1) { - - sass::ostream sels; bool addComma = false; - sels << "Compound selectors may no longer be extended.\n"; - sels << "Consider `@extend "; - for (auto sel : compound->elements()) { - if (addComma) sels << ", "; - sels << sel->to_sass(); - addComma = true; - } - sels << "` instead.\n"; - sels << "See http://bit.ly/ExtendCompound for details."; - - warning(sels.str(), compound->pstate()); - - // Make this an error once deprecation is over - for (SimpleSelectorObj simple : compound->elements()) { - // Pass every selector we ever see to extender (to make them findable for extend) - ctx.extender.addExtension(selector(), simple, mediaStack.back(), e->isOptional()); - } - - } - else { - // Pass every selector we ever see to extender (to make them findable for extend) - ctx.extender.addExtension(selector(), compound->first(), mediaStack.back(), e->isOptional()); - } - - } - else { - error("complex selectors may not be extended.", complex->pstate(), traces); - } - } - } - - return nullptr; - - } - - Statement* Expand::operator()(Definition* d) - { - Env* env = environment(); - Definition_Obj dd = SASS_MEMORY_COPY(d); - env->local_frame()[d->name() + - (d->type() == Definition::MIXIN ? "[m]" : "[f]")] = dd; - - if (d->type() == Definition::FUNCTION && ( - Prelexer::calc_fn_call(d->name().c_str()) || - d->name() == "element" || - d->name() == "expression" || - d->name() == "url" - )) { - deprecated( - "Naming a function \"" + d->name() + "\" is disallowed and will be an error in future versions of Sass.", - "This name conflicts with an existing CSS function with special parse rules.", - false, d->pstate() - ); - } - - // set the static link so we can have lexical scoping - dd->environment(env); - return 0; - } - - Statement* Expand::operator()(Mixin_Call* c) - { - - if (recursions > maxRecursion) { - throw Exception::StackError(traces, *c); - } - - recursions ++; - - Env* env = environment(); - sass::string full_name(c->name() + "[m]"); - if (!env->has(full_name)) { - error("no mixin named " + c->name(), c->pstate(), traces); - } - Definition_Obj def = Cast((*env)[full_name]); - Block_Obj body = def->block(); - Parameters_Obj params = def->parameters(); - - if (c->block() && c->name() != "@content" && !body->has_content()) { - error("Mixin \"" + c->name() + "\" does not accept a content block.", c->pstate(), traces); - } - ExpressionObj rv = c->arguments()->perform(&eval); - Arguments_Obj args = Cast(rv); - sass::string msg(", in mixin `" + c->name() + "`"); - traces.push_back(Backtrace(c->pstate(), msg)); - ctx.callee_stack.push_back({ - c->name().c_str(), - c->pstate().getPath(), - c->pstate().getLine(), - c->pstate().getColumn(), - SASS_CALLEE_MIXIN, - { env } - }); - - Env new_env(def->environment()); - env_stack.push_back(&new_env); - if (c->block()) { - Parameters_Obj params = c->block_parameters(); - if (!params) params = SASS_MEMORY_NEW(Parameters, c->pstate()); - // represent mixin content blocks as thunks/closures - Definition_Obj thunk = SASS_MEMORY_NEW(Definition, - c->pstate(), - "@content", - params, - c->block(), - Definition::MIXIN); - thunk->environment(env); - new_env.local_frame()["@content[m]"] = thunk; - } - - bind(sass::string("Mixin"), c->name(), params, args, &new_env, &eval, traces); - - Block_Obj trace_block = SASS_MEMORY_NEW(Block, c->pstate()); - Trace_Obj trace = SASS_MEMORY_NEW(Trace, c->pstate(), c->name(), trace_block); - - env->set_global("is_in_mixin", bool_true); - if (Block* pr = block_stack.back()) { - trace_block->is_root(pr->is_root()); - } - block_stack.push_back(trace_block); - for (auto bb : body->elements()) { - if (StyleRule* r = Cast(bb)) { - r->is_root(trace_block->is_root()); - } - Statement_Obj ith = bb->perform(this); - if (ith) trace->block()->append(ith); - } - block_stack.pop_back(); - env->del_global("is_in_mixin"); - - ctx.callee_stack.pop_back(); - env_stack.pop_back(); - traces.pop_back(); - - recursions --; - return trace.detach(); - } - - Statement* Expand::operator()(Content* c) - { - Env* env = environment(); - // convert @content directives into mixin calls to the underlying thunk - if (!env->has("@content[m]")) return 0; - Arguments_Obj args = c->arguments(); - if (!args) args = SASS_MEMORY_NEW(Arguments, c->pstate()); - - Mixin_Call_Obj call = SASS_MEMORY_NEW(Mixin_Call, - c->pstate(), - "@content", - args); - - Trace_Obj trace = Cast(call->perform(this)); - return trace.detach(); - } - - // process and add to last block on stack - inline void Expand::append_block(Block* b) - { - if (b->is_root()) call_stack.push_back(b); - for (size_t i = 0, L = b->length(); i < L; ++i) { - Statement* stm = b->at(i); - Statement_Obj ith = stm->perform(this); - if (ith) block_stack.back()->append(ith); - } - if (b->is_root()) call_stack.pop_back(); - } - -} diff --git a/src/expand.hpp b/src/expand.hpp deleted file mode 100644 index b1ab7796d9..0000000000 --- a/src/expand.hpp +++ /dev/null @@ -1,98 +0,0 @@ -#ifndef SASS_EXPAND_H -#define SASS_EXPAND_H - -#include - -#include "ast.hpp" -#include "eval.hpp" -#include "operation.hpp" -#include "environment.hpp" - -namespace Sass { - - class Listize; - class Context; - class Eval; - struct Backtrace; - - class Expand : public Operation_CRTP { - public: - - Env* environment(); - SelectorListObj& selector(); - SelectorListObj& original(); - SelectorListObj popFromSelectorStack(); - SelectorStack getOriginalStack(); - SelectorStack getSelectorStack(); - void pushNullSelector(); - void popNullSelector(); - void pushToSelectorStack(SelectorListObj selector); - - SelectorListObj popFromOriginalStack(); - - void pushToOriginalStack(SelectorListObj selector); - - Context& ctx; - Backtraces& traces; - Eval eval; - size_t recursions; - bool in_keyframes; - bool at_root_without_rule; - bool old_at_root_without_rule; - - // it's easier to work with vectors - EnvStack env_stack; - BlockStack block_stack; - CallStack call_stack; - private: - SelectorStack selector_stack; - public: - SelectorStack originalStack; - MediaStack mediaStack; - - Boolean_Obj bool_true; - - private: - - sass::vector mergeMediaQueries(const sass::vector& lhs, const sass::vector& rhs); - - public: - Expand(Context&, Env*, SelectorStack* stack = nullptr, SelectorStack* original = nullptr); - ~Expand() { } - - Block* operator()(Block*); - Statement* operator()(StyleRule*); - - Statement* operator()(MediaRule*); - - // Css StyleRule is already static - // Statement* operator()(CssMediaRule*); - - Statement* operator()(SupportsRule*); - Statement* operator()(AtRootRule*); - Statement* operator()(AtRule*); - Statement* operator()(Declaration*); - Statement* operator()(Assignment*); - Statement* operator()(Import*); - Statement* operator()(Import_Stub*); - Statement* operator()(WarningRule*); - Statement* operator()(ErrorRule*); - Statement* operator()(DebugRule*); - Statement* operator()(Comment*); - Statement* operator()(If*); - Statement* operator()(ForRule*); - Statement* operator()(EachRule*); - Statement* operator()(WhileRule*); - Statement* operator()(Return*); - Statement* operator()(ExtendRule*); - Statement* operator()(Definition*); - Statement* operator()(Mixin_Call*); - Statement* operator()(Content*); - - void append_block(Block*); - - }; - -} - -#endif diff --git a/src/extender.cpp b/src/extender.cpp index 95eece1782..ddd326438a 100644 --- a/src/extender.cpp +++ b/src/extender.cpp @@ -1,165 +1,445 @@ -// sass.hpp must go before all system headers to get the -// __EXTENSIONS__ fix on Solaris. -#include "sass.hpp" -#include "ast.hpp" - +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ #include "extender.hpp" + #include "permutate.hpp" +#include "callstack.hpp" +#include "exceptions.hpp" #include "dart_helpers.hpp" +#include "ast_selectors.hpp" +#include "ast_helpers.hpp" +#include "ast_css.hpp" + +#include "debugger.hpp" namespace Sass { - // ########################################################################## - // Constructor without default [mode]. + template + sass::string VecToString(sass::vector exts) { + sass::string msg = "["; + bool joiner = false; + for (auto& entry : exts) { + if (joiner) msg += ", "; + msg += entry.selector->inspect(); + joiner = true; + } + return msg + "]"; + + } + + + template + sass::string VecVecToString(sass::vector exts) { + sass::string msg = "["; + bool joiner = false; + for (auto& entry : exts) { + if (joiner) msg += ", "; + msg += VecToString(entry); + joiner = true; + } + return msg + "]"; + + } + + template + sass::string VecToString2(sass::vector exts) { + sass::string msg = "["; + bool joiner = false; + for (auto& entry : exts) { + if (joiner) msg += ", "; + msg += entry->inspect(); + joiner = true; + } + return msg + "]"; + + } + + template + sass::string VecVecToString2(sass::vector exts) { + sass::string msg = "["; + bool joiner = false; + for (auto& entry : exts) { + if (joiner) msg += ", "; + msg += VecToString2(entry); + joiner = true; + } + return msg + "]"; + + } + + + sass::string ExtSelToStr(const ExtSelExtMap& map) { + sass::string msg; + for (auto& entry : map) { + msg += entry.first->inspect(); + msg += ": { "; + for (auto& pair : entry.second) { + ComplexSelector* sel = pair.first; + Extension* extension = pair.second; + msg += sel->inspect(); + msg += ": "; + msg += extension->toString(); + } + msg += " }"; + msg += ", "; + } + return msg; + } + + sass::string ExtensionStore::toString() { + sass::string msg; + for (auto& entry : extensionsBySimpleSelector) { + msg += entry.first->inspect(); + msg += ": { "; + for (auto& pair : entry.second) { + ComplexSelector* sel = pair.first; + Extension* extension = pair.second; + msg += sel->inspect(); + msg += ": "; + msg += extension->toString(); + } + msg += " }"; + msg += ", "; + } + return msg; + } + + sass::string Extension::toString() const { + sass::string msg; + msg += extender.selector->inspect(); + msg += "{@extend "; + msg += target->inspect(); + if (isOptional) { + msg += " !optional"; + } + msg += "}"; + return msg; + } + + ///////////////////////////////////////////////////////////////////////// + // Constructor with specific [mode]. // [traces] are needed to throw errors. - // ########################################################################## - Extender::Extender(Backtraces& traces) : - mode(NORMAL), - traces(traces), - selectors(), - extensions(), + ///////////////////////////////////////////////////////////////////////// + ExtensionStore::ExtensionStore(ExtendMode mode, BackTraces& traces) : + mode(mode), + traces(&traces), + selectors54(), +// extensionsBySimpleSelector(), extensionsByExtender(), mediaContexts(), sourceSpecificity(), originals() - {} + { + // std::cerr << "!! Create extension store\n"; + } - // ########################################################################## - // Constructor with specific [mode]. - // [traces] are needed to throw errors. - // ########################################################################## - Extender::Extender(ExtendMode mode, Backtraces& traces) : - mode(mode), - traces(traces), - selectors(), - extensions(), + ExtensionStore::ExtensionStore() : + mode(NORMAL), + traces(nullptr), + selectors54(), + // extensionsBySimpleSelector(), extensionsByExtender(), mediaContexts(), sourceSpecificity(), originals() - {} + { + // std::cerr << "!! Create empty extension store\n"; + } - // ########################################################################## + // extensionsWhereTarget((target) = > !originalSelectors.contains(target)) + void ExtensionStore::addNonOriginalSelectors( + ExtSmplSelSet originalSelectors, + ExtSet& unsatisfiedExtensions) + { + for (auto& entry : extensionsBySimpleSelector) { + if (originalSelectors.count(entry.first)) continue; + for (auto& extension : entry.second) { + // ToDo: check why we have this here!? + if (extension.second->isOptional) continue; + if (extension.second->target.isNull()) { + //std::cerr << "has no target, skip\n"; + continue; + } + unsatisfiedExtensions.insert(extension.second); + } + } + } + + // extensionsWhereTarget((target) = > !originalSelectors.contains(target)) + void ExtensionStore::delNonOriginalSelectors( + ExtSmplSelSet originalSelectors, + ExtSet& unsatisfiedExtensions) + { + for (auto& entry : extensionsBySimpleSelector) { +// std::cerr << "! Check " << entry.first->inspect() << "\n"; + if (!originalSelectors.count(entry.first)) continue; + for (auto& extension : entry.second) { +// std::cerr << "! Check ext " << extension.first->inspect() << "\n"; + if (extension.second.isNull()) continue; + if (extension.second->isOptional) continue; + // if (extension.second->target.isNull()) continue; +// std::cerr << ">> Yield " << extension.second->toString() << "\n"; + unsatisfiedExtensions.erase(extension.second); + } + } + } + + ///////////////////////////////////////////////////////////////////////// // 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, + ///////////////////////////////////////////////////////////////////////// + SelectorListObj ExtensionStore::extend( + const SelectorListObj& selector, const SelectorListObj& source, const SelectorListObj& targets, - Backtraces& traces) + Logger& logger) { - return extendOrReplace(selector, source, targets, ExtendMode::TARGETS, traces); + return extendOrReplace(selector, source, targets, ExtendMode::TARGETS, logger); } - // EO Extender::extend + // EO ExtensionStore::extend - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // Returns a copy of [selector] with [targets] replaced by [source]. - // ########################################################################## - SelectorListObj Extender::replace( - SelectorListObj& selector, + ///////////////////////////////////////////////////////////////////////// + SelectorListObj ExtensionStore::replace( + const SelectorListObj& selector, const SelectorListObj& source, const SelectorListObj& targets, - Backtraces& traces) + Logger& logger) { - return extendOrReplace(selector, source, targets, ExtendMode::REPLACE, traces); + return extendOrReplace(selector, source, targets, ExtendMode::REPLACE, logger); } - // EO Extender::replace + // EO ExtensionStore::replace + - // ########################################################################## + sass::string SetToString(ExtCplxSelSet& set) { + sass::string msg; + for (auto& item : set) { + msg += item->inspect(); + msg += ", "; + } + return msg; + } + + ///////////////////////////////////////////////////////////////////////// // A helper function for [extend] and [replace]. - // ########################################################################## - SelectorListObj Extender::extendOrReplace( - SelectorListObj& selector, + ///////////////////////////////////////////////////////////////////////// + SelectorListObj ExtensionStore::extendOrReplace( + const SelectorListObj& selector, const SelectorListObj& source, const SelectorListObj& targets, const ExtendMode mode, - Backtraces& traces) + Logger& logger) { - ExtSelExtMapEntry extenders; - for (auto complex : source->elements()) { - // Extension.oneOff(complex as ComplexSelector) - extenders.insert(complex, Extension(complex)); + // sass::vector results; + + ExtensionStoreObj extender = SASS_MEMORY_NEW(ExtensionStore, mode, logger); + + if (selector->isInvisible() == false) { + for (auto& original : selector->elements()) + extender->originals.insert(original); } - for (auto complex : targets->elements()) { + SelectorListObj results = selector; // SASS_MEMORY_COPY(selector); - // This seems superfluous, check is done before!? - // if (complex->length() != 1) { - // error("complex selectors may not be extended.", complex->pstate(), traces); - // } + //std::cerr << "selector " << selector->inspect() << "\n"; + //std::cerr << "source " << source->inspect() << "\n"; + //std::cerr << "targets " << targets->inspect() << "\n"; - if (const CompoundSelector* compound = complex->first()->getCompound()) { + for (auto& complex : targets->elements()) { + + if (auto* compound = complex->getSingleCompound()) { ExtSelExtMap extensions; - for (const SimpleSelectorObj& simple : compound->elements()) { - extensions.insert(std::make_pair(simple, extenders)); + for (auto& simple : compound->elements()) { + for (auto& src : source->elements()) { + // std::cerr << "Add ext key " << simple->inspect() << "\n"; + extensions[simple][src] = SASS_MEMORY_NEW(Extension, + complex->pstate(), src, simple, {}, false, true); + } } - Extender extender(mode, traces); + // std::cerr << "1) extend " << ExtSelToStr(extensions) << "\n"; + extender->extendList(results, extensions, {}, results->elements()); - if (!selector->is_invisible()) { - for (auto sel : selector->elements()) { - extender.originals.insert(sel); - } - } + } + else { + throw Exception::RuntimeException(logger, + "Can't extend complex selector " + + complex->inspect() + "."); + } + } - selector = extender.extendList(selector, extensions, {}); + return results; + /* + sass::vector< CompoundSelectorObj> compoundTargets; + for (auto& complex : targets->elements()) { + if (complex->empty()) continue; + if (complex->size() > 1) { + throw Exception::RuntimeException(logger, + "Can't extend complex selector " + + complex->inspect() + "."); } + if (CompoundSelector* compound = complex->first()->selector()) { + compoundTargets.emplace_back(compound); + } + } + + ExtSelExtMap extensions; + for (auto& compound : compoundTargets) { + for (auto& simple : compound->elements()) { + for (auto& complex : source->elements()) { + extensions[simple][complex] = + SASS_MEMORY_NEW(Extension, + complex->pstate(), + complex, simple, {}, false, true); + // extensions[simple][complex]->specificity = complex->minSpecificity(); + } + } } - return selector; + ExtensionStoreObj extender = SASS_MEMORY_NEW(ExtensionStore, mode, logger); + if (!selector->isInvisible()) { + for (auto& complex : selector->elements()) { + extender->originals.insert(complex); + } + } + + // std::cerr << "Sel: " << selector->inspect() << "\n"; + // std::cerr << "Ext: " << ExtSelToStr(extensions) << "\n"; + // std::cerr << "Org: " << SetToString(extender->originals) << "\n"; + + extender->extendList(selector, extensions, + {}, selector->elements()); + + 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 ExtensionStore::getSimpleSelectors() const { ExtSmplSelSet set; - for (auto& entry : selectors) { + for (auto& entry : selectors54) { 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 + ///////////////////////////////////////////////////////////////////////// + bool ExtensionStore::checkForUnsatisfiedExtends2(Extension& unsatisfied) const { - if (selectors.empty()) return false; + //ExtSmplSelSet originals; + //for (auto& entry : selectors) { + // originals.insert(entry.first); + //} + //for (auto& mod : upstreams) + // + + // if (selectors.empty()) return false; // Remove? ExtSmplSelSet originals = getSimpleSelectors(); - for (auto target : extensions) { - SimpleSelector* key = target.first; - ExtSelExtMapEntry& val = target.second; - if (val.empty()) continue; - if (originals.find(key) == originals.end()) { - const Extension& extension = val.front().second; - if (extension.isOptional) continue; - unsatisfied = extension; - return true; - } - } + + +// for (auto target : extensionsBySimpleSelector) { +// SimpleSelector* key = target.first; +// const ExtSelExtMapEntry& val = target.second; +// for (auto qwe : val) { +// auto asd = qwe.second; +// if (asd->isOriginal) { +// if (!asd->isConsumed) { +// if (!asd->isOptional) { +// unsatisfied = asd; +// return true; +// } +// } +// } +// } +// // if (originals.find(key) == originals.end()) { + // const Extension& extension = val.begin()->second; + // if (extension.isOptional) continue; + // unsatisfied = extension; + // return true; + // } +// } + // for (auto asd : upstream) return false; } // EO checkUnsatisfiedExtends - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// + // 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* ExtensionStore::mergeExtension( + Extension* lhs, Extension* rhs) + { + + if (rhs == nullptr) return lhs; + if (lhs == nullptr) return 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 = SASS_MEMORY_NEW(Extension, *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 [selector] to this extender, with [selectorSpan] as the span covering // the selector and [ruleSpan] as the span covering the entire style rule. // Extends [selector] using any registered extensions, then returns an empty @@ -167,15 +447,13 @@ namespace Sass { // extensions are added, the returned rule is automatically updated. // The [mediaContext] is the media query context in which the selector was // defined, or `null` if it was defined at the top level of the document. - // ########################################################################## - void Extender::addSelector( + ///////////////////////////////////////////////////////////////////////// + void ExtensionStore::addSelector( const SelectorListObj& selector, const CssMediaRuleObj& mediaContext) { - // Note: dart-sass makes a copy here AFAICT - // Note: probably why we have originalStack - // SelectorListObj original = selector; + // std::cerr << "add selector " << selector->inspect() << "\n"; if (!selector->isInvisible()) { for (auto complex : selector->elements()) { @@ -183,41 +461,51 @@ namespace Sass { } } - if (!extensions.empty()) { - - SelectorListObj res = extendList(selector, extensions, mediaContext); - - selector->elements(res->elements()); - + if (!extensionsBySimpleSelector.empty()) { + extendList(selector, extensionsBySimpleSelector, + mediaContext, selector->elements()); + // Dart-Sass upgrades error here } if (!mediaContext.isNull()) { - mediaContexts.insert(selector, mediaContext); + mediaContexts[selector] = mediaContext; } - registerSelector(selector, selector); + _registerSelector(selector, selector); + // Dart-Sass returns wrapped in modifiable } - // EO addSelector + // EO addSelector (checked) - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // Registers the [SimpleSelector]s in [list] // to point to [rule] in [selectors]. - // ########################################################################## - void Extender::registerSelector( + ///////////////////////////////////////////////////////////////////////// + void ExtensionStore::_registerSelector( const SelectorListObj& list, - const SelectorListObj& rule) + const SelectorListObj& rule, + bool onlyPublic) { if (list.isNull() || list->empty()) return; + // std::cerr << "Reg selector " << rule.ptr() << " - " << rule->inspect() << " => " << list->inspect() << "\n"; 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 (auto compound = component->selector()) { + for (const SimpleSelectorObj& simple : compound->elements()) { + // Creating this structure can take up to 5% + if (auto ph = simple->isaPlaceholderSelector()) { + if (!onlyPublic || !ph->isPrivate93()) { + selectors54[simple].insert(rule); + } + } + else { + selectors54[simple].insert(rule); + } + if (auto pseudo = simple->isaPseudoSelector()) { if (pseudo->selector()) { - auto sel = pseudo->selector(); - registerSelector(sel, rule); + auto selectorInPseudo = pseudo->selector(); + //std::cerr << "Register inner pseudo\n"; + _registerSelector(selectorInPseudo, rule); } } } @@ -225,115 +513,69 @@ namespace Sass { } } } - // EO registerSelector + // EO _registerSelector (checked) - // ########################################################################## - // 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( + ///////////////////////////////////////////////////////////////////////// + void ExtensionStore::addExtension( const SelectorListObj& extender, const SimpleSelectorObj& target, - const CssMediaRuleObj& mediaQueryContext, + const CssMediaRuleObj& mediaContext, + const ExtendRuleObj& extend, bool is_optional) { - auto rules = selectors.find(target); - bool hasRule = rules != selectors.end(); + auto rules = selectors54.find(target); + bool hasRule = rules != selectors54.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]; + // Get existing extensions for the given target (SimpleSelector) + ExtSelExtMapEntry& sources = extensionsBySimpleSelector[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 (complex->isUseless()) continue; + // std::cerr << "+++ " << complex->inspect() << "\n"; + ExtensionObj extension = SASS_MEMORY_NEW(Extension, + extend->pstate(), + complex, target, mediaContext, false, is_optional); + auto existingExtension = sources.find(complex); + if (existingExtension != sources.end()) { // 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); + sources[complex] = mergeExtension( + existingExtension.value(), extension); // ToDo: implement behavior once use case is found!? + // ToDo: mergeExtension needs error checks etc. continue; } - sources.insert(complex, state); + // std::cerr << "==== " << complex->inspect() << " adding " << extension->toString() << "\n"; + sources[complex] = extension; for (auto& component : complex->elements()) { - if (auto compound = component->getCompound()) { + if (auto compound = component->selector()) { for (auto& simple : compound->elements()) { - extensionsByExtender[simple].push_back(state); + extensionsByExtender[simple].emplace_back(extension); if (sourceSpecificity.find(simple) == sourceSpecificity.end()) { // Only source specificity for the original selector is relevant. // Selectors generated by `@extend` don't get new specificity. + // ToDo: two map structures with the same key is inefficient sourceSpecificity[simple] = complex->maxSpecificity(); } } @@ -341,7 +583,7 @@ namespace Sass { } if (hasRule || hasExistingExtensions) { - newExtensions.insert(complex, state); + newExtensions[complex] = extension; } } @@ -358,48 +600,25 @@ namespace Sass { if (existingExtensions != extensionsByExtender.end()) { if (hasExistingExtensions && !existingExtensions->second.empty()) { // Seems only relevant for sass 4.0 modules - // auto additionalExtensions = - extendExistingExtensions(existingExtensions->second, newExtensionsByTarget); + ExtSelExtMap additionalExtensions = _extendExistingExtensions( + existingExtensions->second, newExtensionsByTarget); // Seems only relevant for sass 4.0 modules - /* if (!additionalExtensions.empty()) { - mapCopyExts(newExtensionsByTarget, additionalExtensions); - } */ + if (!additionalExtensions.empty()) { + //std::cerr << "--- Check if we are doing it right?\n"; + mapAddAll2(newExtensionsByTarget, additionalExtensions); + } } } if (hasRule) { - extendExistingStyleRules(selectors[target], newExtensionsByTarget); + _extendExistingSelectors(selectors54[target], newExtensionsByTarget); } } - // EO addExtension + // EO addExtension (checked) - // ########################################################################## - // 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 happened (for example because 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 @@ -413,13 +632,12 @@ namespace Sass { // .z.b {@extend .c} // // Returns `null` (Note: empty map) if there are no extensions to add. - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // Note: maybe refactor to return `bool` (and pass reference) // Note: dart-sass throws an error in here - // ########################################################################## - ExtSelExtMap Extender::extendExistingExtensions( - // Taking in a reference here makes MSVC debug stuck!? - const sass::vector& oldExtensions, + ///////////////////////////////////////////////////////////////////////// + ExtSelExtMap ExtensionStore::_extendExistingExtensions( + const sass::vector& oldExtensions, const ExtSelExtMap& newExtensions) { @@ -429,23 +647,30 @@ namespace Sass { // 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]; + + Extension* extension = oldExtensions[i]; + Extender& extender = extension->extender; + SimpleSelector* target = extension->target; + CssMediaRule* mediaContext = extension->mediaContext; + + // Get all registered extensions for this (SimpleSelector) target + ExtSelExtMapEntry& sources = extensionsBySimpleSelector[target]; + + // Do the actual extending of the selector on extender sass::vector selectors(extendComplex( - extension.extender, - newExtensions, - extension.mediaContext - )); + extender.selector, newExtensions, mediaContext)); + // Dart-sass upgrades error? if (selectors.empty()) { + // Should not happen? continue; } - // ToDo: "catch" error from extend - bool first = false, containsExtension = - ObjEqualityFn(selectors.front(), extension.extender); - for (const ComplexSelectorObj& complex : selectors) { + ObjEqualityFn(selectors.front(), extender.selector); + + for (ComplexSelectorObj& complex : selectors) { + // If the output contains the original complex // selector, there's no need to recreate it. if (containsExtension && first) { @@ -453,103 +678,238 @@ namespace Sass { continue; } - const Extension withExtender = - extension.withExtender(complex); - if (sources.hasKey(complex)) { - sources.insert(complex, mergeExtension( - sources.get(complex), withExtender)); + Extension* withExtender = + extension->withExtender(complex); + auto it = sources.find(complex); + if (it != sources.end()) { + sources[complex] = mergeExtension( + it->second, withExtender); } else { - sources.insert(complex, withExtender); - /* + sources[complex] = withExtender; // Seems only relevant for sass 4.0 modules for (auto& component : complex->elements()) { - if (auto compound = component->getCompound()) { + if (auto compound = component->selector()) { for (auto& simple : compound->elements()) { - extensionsByExtender[simple].push_back(withExtender); + extensionsByExtender[simple].emplace_back(withExtender); } } } - if (newExtensions.find(extension.target) != newExtensions.end()) { - additionalExtensions[extension.target].insert(complex, withExtender); + if (newExtensions.find(target) != newExtensions.end()) { + ExtSelExtMapEntry& entry = additionalExtensions[target]; + entry.emplace(std::make_pair(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); + // sources.erase(extension.extender); } - */ } return additionalExtensions; } - // EO extendExistingExtensions + // EO _extendExistingExtensions (checked/complex) + + ///////////////////////////////////////////////////////////////////////// + // Extend [extensions] using [newExtensions]. + ///////////////////////////////////////////////////////////////////////// + // Note: dart-sass throws an error in here + ///////////////////////////////////////////////////////////////////////// + void ExtensionStore::_extendExistingSelectors( + const ExtListSelSet& selectors, + const ExtSelExtMap& newExtensions) + { + // Is a modifyableCssStyleRUle in dart sass + for (const SelectorListObj& selector : selectors) { + CssMediaRule* mediaContext = nullptr; + auto it = mediaContexts.find(selector); + if (it != mediaContexts.end()) { + mediaContext = it->second; + } + + // std::cerr << "Extend existing selector " << selector->toString() << "\n"; + + // If no extends happened (for example because unification + // failed), we don't need to re-register the selector again. + // LibSass Note: we pass the result instead of assigning + // Instead it passes back if the extend succeeded therefore + // we do not need the `identical` check that Dart-Sass has + // std::cerr << "extend selector [" << selector->inspect() << "]\n"; + if (extendList(selector, newExtensions, mediaContext, selector->elements())) { + //std::cerr << "Register existing selector " << selector->toString() << "\n"; + _registerSelector(selector, selector); + } + + } + } + // EO _extendExistingSelectors (checked) + + ///////////////////////////////////////////////////////////////////////// + /// Extends [this] with all the extensions in [extensions]. + /// These extensions will extend all selectors already in [this], + /// but they will *not* extend other extensions from [extenders]. + ///////////////////////////////////////////////////////////////////////// + + void ExtensionStore::addExtensions( + sass::vector& extensionStores) { + + // Extensions already in [this] whose extenders are extended by + // [extensions], and thus which need to be updated. + sass::vector extensionsToExtend; + + // Selectors that contain simple selectors that are extended by + // [extensions], and thus which need to be extended themselves. + ExtListSelSet selectorsToExtend; + + // An extension map with the same structure as [_extensions] that only + // includes extensions from [extensionStores]. + // Map> ? newExtensions; + ExtSelExtMap newExtensions; + + bool hasSelectors = false; + bool hasExtensions = false; + + for (ExtensionStore* extensionStore : extensionStores) { + if (extensionStore->isEmpty()) continue; + mapAddAll(sourceSpecificity, extensionStore->sourceSpecificity); + + auto& extensions = extensionStore->extensionsBySimpleSelector; + for (auto extension : extensions) { + auto& target = extension.first; + auto& newSources = extension.second; + + // Private selectors can't be extended across module boundaries. + if (auto* placeholder = target->isaPlaceholderSelector()) { + if (placeholder->isPrivate93()) return; // continue? + } + + // Find existing extensions to extend. + auto extensionsForTargetIt = extensionsByExtender.find(target); + if ((hasExtensions = (extensionsForTargetIt != extensionsByExtender.end()))) { + auto& extensionsForTarget = extensionsForTargetIt->second; + extensionsToExtend.insert(extensionsToExtend.end(), + extensionsForTarget.begin(), extensionsForTarget.end()); + } + + // Find existing selectors to extend. + auto selectorsForTargetIt = selectors54.find(target); + if ((hasSelectors = (selectorsForTargetIt != selectors54.end()))) { + ExtListSelSet& selectorsForTarget = selectorsForTargetIt->second; + selectorsToExtend.insert(selectorsForTarget.begin(), selectorsForTarget.end()); + } + + // Add [newSources] to [_extensions]. + auto existingSourcesIt = extensionsBySimpleSelector.find(target); + if (existingSourcesIt == extensionsBySimpleSelector.end()) { + //std::cerr << "==== Set newSources for target " << target->inspect() << " ON " << this << "\n"; + extensionsBySimpleSelector[target] = newSources; + if (hasExtensions || hasSelectors) { + //std::cerr << "= Set newSources for newExt\n"; + newExtensions[target] = newSources; + } + } + else { + auto& existingSources = existingSourcesIt->second; + for (auto& source : newSources) { + auto& extender = source.first; + auto& extension = source.second; + // If [extender] already extends [target] in [_extensions], we don't + // need to re-run the extension. + if (existingSources.count(extender) == 0) { + existingSources[extender] = extension; + } + + if (!hasExtensions || !hasSelectors) { + newExtensions[target][extender] = extension; + } + } + } + } + } - // ########################################################################## + // We can ignore the return value here because it's only useful for extend + // loops, which can't exist across module boundaries. + _extendExistingExtensions(extensionsToExtend, newExtensions); + _extendExistingSelectors(selectorsToExtend, newExtensions); + + } + // EO addExtensions (checked/new) + + ///////////////////////////////////////////////////////////////////////// // Extends [list] using [extensions]. - // ########################################################################## - SelectorListObj Extender::extendList( + ///////////////////////////////////////////////////////////////////////// + bool ExtensionStore::extendList( const SelectorListObj& list, const ExtSelExtMap& extensions, - const CssMediaRuleObj& mediaQueryContext) + const CssMediaRuleObj& mediaQueryContext, + sass::vector& result) { - + // std::cerr << "extend list\n"; // This could be written more simply using [List.map], but we want to // avoid any allocations in the common case where no extends apply. sass::vector extended; - for (size_t i = 0; i < list->length(); i++) { + for (size_t i = 0; i < list->size(); i++) { const ComplexSelectorObj& complex = list->get(i); + // std::cerr << "extend [" << ExtSelToStr(extensions) << "]\n"; sass::vector result = extendComplex(complex, extensions, mediaQueryContext); + // std::cerr << "extended [" << VecToString2(result) << "]\n"; if (result.empty()) { if (!extended.empty()) { - extended.push_back(complex); + extended.emplace_back(complex); } } else { if (extended.empty()) { for (size_t n = 0; n < i; n += 1) { - extended.push_back(list->get(n)); + extended.emplace_back(list->get(n)); } } - for (auto sel : result) { - extended.push_back(sel); + for (auto& sel : result) { + extended.emplace_back(std::move(sel)); } } } if (extended.empty()) { - return list; + return false; } - SelectorListObj rv = SASS_MEMORY_NEW(SelectorList, list->pstate()); - rv->concat(trim(extended, originals)); - return rv; + // std::cerr << "RESULT "; + // for (auto& qwe : extended) { + // std::cerr << qwe->inspect() << ", "; + // } + // std::cerr << "\n"; + + result = std::move(extended); + trim(result, originals); + + return true; } - // EO extendList + // EO extendList (review) - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // Extends [complex] using [extensions], and // returns the contents of a [SelectorList]. - // ########################################################################## - sass::vector Extender::extendComplex( + ///////////////////////////////////////////////////////////////////////// + sass::vector ExtensionStore::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] + if (complex->leadingCombinators().size() > 1) return {}; + + // The complex selectors that each compound selector in [complex.components] // can expand to. // // For example, given @@ -567,179 +927,278 @@ namespace Sass { // This could be written more simply using [List.map], but we want to avoid // any allocations in the common case where no extends apply. + //if (complex->inspect() == "%x %y") { + // std::cerr << "nope\n"; + //} + + //std::cerr << "extend complex [" << complex->inspect() << "]\n"; + + //std::cerr << "_extendComplex\n"; + bool addedToExtendedNotExpanded = false; sass::vector result; sass::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)) { + for (size_t i = 0; i < complex->size(); i += 1) { + const CplxSelComponentObj& component = complex->get(i); + if (CompoundSelector* compound = component->selector()) { + // std::cerr << "extend compound [" << compound->inspect() << "]\n"; + //std::cerr << "extend compound [" << component->toString() << "]\n"; sass::vector extended = extendCompound( - compound, extensions, mediaQueryContext, isOriginal); + component, extensions, mediaQueryContext, + complex->leadingCombinators(), isOriginal); + + //std::cerr << "- extended result " << VecToString2(extended) << "\n"; + //std::cerr << "- extendedNotExpanded " << VecVecToString2(extendedNotExpanded) << "\n"; + + + for (auto res : extended) { + //std::cerr << "extended compound [" << res->inspect() << "]\n"; + } if (extended.empty()) { if (!extendedNotExpanded.empty()) { + auto s = SASS_MEMORY_NEW(ComplexSelector, complex->pstate(), + {}, { component }, complex->hasLineBreak()); + //std::cerr << "ADD 1 [" << s->inspect() << "]\n"; extendedNotExpanded.push_back({ - compound->wrapInComplex() + + s + + // compound->wrapInComplex( + // complex->leadingCombinators(), + // component->combinators()) }); + //std::cerr << "- ADDED 1 " << VecVecToString2(extendedNotExpanded) << "\n"; } } else { + // Note: dart-sass checks for null!? - if (extendedNotExpanded.empty()) { + if (!extendedNotExpanded.empty()) { + //std::cerr << "ADD 2\n"; + addedToExtendedNotExpanded = true; + extendedNotExpanded.push_back(extended); + // for (size_t n = 0; n < i; n++) { + // extendedNotExpanded.push_back({ + // // complex->at(n)->wrapInComplex2() + // complex->at(n)->wrapInComplex( + // complex->leadingCombinators()) + // }); + // } + } + else if (i != 0) { + sass::vector components; for (size_t n = 0; n < i; n++) { - extendedNotExpanded.push_back({ - complex->at(n)->wrapInComplex() - }); + components.push_back(complex->get(n)); + } + auto s = SASS_MEMORY_NEW(ComplexSelector, complex->pstate(), + complex->leadingCombinators(), components, + complex->hasLineBreak()); + //std::cerr << "ADD 3 [" << s->inspect() << "]\n"; + addedToExtendedNotExpanded = true; + extendedNotExpanded = { { + s + }, extended }; + } + else if (complex->leadingCombinators().empty()) { + //std::cerr << "ADD 4 " << VecToString2(extended) << "\n"; + addedToExtendedNotExpanded = true; + extendedNotExpanded = { extended }; //.emplace_back(extended); + } + else { + //std::cerr << "ADD 5\n"; + addedToExtendedNotExpanded = true; + sass::vector add; + for (auto newComplex : extended) { + if (newComplex->leadingCombinators().empty() || + ListEquality(complex->leadingCombinators(), newComplex->leadingCombinators(), PtrObjEqualityFn)) { + if (complex->hasPreLineFeed() || newComplex->hasPreLineFeed()) { + std::cerr << "has pre line feed\n"; + } + add.push_back(SASS_MEMORY_NEW(ComplexSelector, complex->pstate(), + complex->leadingCombinators(), newComplex->elements(), + complex->hasLineBreak() || newComplex->hasLineBreak() || + complex->hasPreLineFeed() || newComplex->hasPreLineFeed())); + } + else { + //add.push_back(newComplex); + } } + extendedNotExpanded.emplace_back(add); } - extendedNotExpanded.push_back(extended); } } else { + std::cerr << "WHAT THE FUCK IS THIS?\n"; // Note: dart-sass checks for null!? if (!extendedNotExpanded.empty()) { extendedNotExpanded.push_back({ - component->wrapInComplex() + // component->wrapInComplex2() + component->wrapInComplex( + complex->leadingCombinators()) }); } } } + //std::cerr << "=> extendedNotExpanded" << VecVecToString2(extendedNotExpanded) << "\n"; + // Note: dart-sass checks for null!? if (extendedNotExpanded.empty()) { return {}; } + + for (auto a : extendedNotExpanded) { + for (auto b : a) { + //std::cerr << "not expanded [" << b->inspect() << "]\n"; + } + } + + for (auto b : result) { + // std::cerr << "res [" << b->inspect() << "]\n"; + } + bool first = true; // ToDo: either change weave or paths to work with the same data? sass::vector> paths = permutate(extendedNotExpanded); + for (auto a : paths) { + for (auto b : a) { + //std::cerr << "path [" << b->inspect() << "]\n"; + } + } + for (const sass::vector& path : paths) { // Unpack the inner complex selector to component list - sass::vector> _paths; + sass::vector _paths; for (const ComplexSelectorObj& sel : path) { - _paths.insert(_paths.end(), sel->elements()); + _paths.insert(_paths.end(), sel); + } + + for (auto a : _paths) { + //std::cerr << "_path [" << a->inspect() << "]\n"; } - sass::vector> weaved = weave(_paths); - for (sass::vector& components : weaved) { + sass::vector weaved = weave(_paths); + + for (auto b : weaved) { + //std::cerr << "woven [" << b->inspect() << "]\n"; + } + + for (ComplexSelectorObj& components : weaved) { + auto others(components->elements()); + ComplexSelectorObj cplx = SASS_MEMORY_NEW(ComplexSelector, + complex->pstate(), components->leadingCombinators(), std::move(others)); + // std::cerr << "woven [" << cplx->inspect() << "]\n"; - ComplexSelectorObj cplx = SASS_MEMORY_NEW(ComplexSelector, complex->pstate()); 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); + if (first) { + auto it = originals.begin(); + while (it != originals.end()) { + if (ObjEqualityFn(*it, complex)) break; + it++; + } + if (it != originals.end()) { + // std::cerr << "insert org [" << cplx->inspect() << "]\n"; + originals.insert(cplx); + } + first = false; } - first = false; + // Make sure we don't append any copies auto it = result.begin(); while (it != result.end()) { if (ObjEqualityFn(*it, cplx)) break; - it += 1; + it++; } if (it == result.end()) { + // std::cerr << "insert res [" << cplx->inspect() << "]\n"; result.push_back(cplx); } if (result.size() > 500) { - throw Exception::EndlessExtendError(traces, complex); + traces->push_back(complex->pstate()); + throw Exception::EndlessExtendError(*traces); } } } - return result; - } - // EO extendComplex + // Here we must have two - // ########################################################################## - // 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 + // std::cerr << "Complex: " << VecToString2(result) << "\n"; - // ########################################################################## - // Returns a one-off [Extension] whose extender is composed - // solely of a compound selector containing [simples]. - // ########################################################################## - Extension Extender::extensionForCompound( - // Taking in a reference here makes MSVC debug stuck!? - const sass::vector& simples) const - { - CompoundSelectorObj compound = SASS_MEMORY_NEW(CompoundSelector, SourceSpan("[ext]")); - compound->concat(simples); - Extension extension(compound->wrapInComplex()); - // extension.specificity = sourceSpecificity[simple]; - extension.isOriginal = true; - return extension; + return result; } - // EO extensionForCompound + // EO extendComplex (review) - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // Extends [compound] using [extensions], and returns the // contents of a [SelectorList]. The [inOriginal] parameter // indicates whether this is in an original complex selector, // meaning that [compound] should not be trimmed out. - // ########################################################################## - sass::vector Extender::extendCompound( - const CompoundSelectorObj& compound, + ///////////////////////////////////////////////////////////////////////// + sass::vector ExtensionStore::extendCompound( + const CplxSelComponentObj& component, const ExtSelExtMap& extensions, const CssMediaRuleObj& mediaQueryContext, + const SelectorCombinatorVector& prefixes, bool inOriginal) { + auto compound = component->selector(); + // If there's more than one target and they all need to // match, we track which targets are actually extended. - ExtSmplSelSet targetsUsed2; - - ExtSmplSelSet* targetsUsed = nullptr; - + std::unique_ptr targetsUsed; if (mode != ExtendMode::NORMAL && extensions.size() > 1) { - targetsUsed = &targetsUsed2; + targetsUsed.reset(new ExtSmplSelSet()); } sass::vector result; // The complex selectors produced from each component of [compound]. - sass::vector> options; - - for (size_t i = 0; i < compound->length(); i++) { + sass::vector> options; + for (size_t i = 0; i < compound->size(); i++) { const SimpleSelectorObj& simple = compound->get(i); - auto extended = extendSimple(simple, extensions, mediaQueryContext, targetsUsed); + auto extended = extendSimple(simple, extensions, + mediaQueryContext, targetsUsed.get()); + if (extended.empty()) { if (!options.empty()) { - options.push_back({ extensionForSimple(simple) }); - } + auto foo = extenderForSimple(simple); + //std::cerr << "Extended is nullptr "<< simple->toString() <<"\n"; + options.push_back({ foo }); + } } else { if (options.empty()) { if (i != 0) { - sass::vector in; - for (size_t n = 0; n < i; n += 1) { - in.push_back(compound->get(n)); - } - options.push_back({ extensionForCompound(in) }); + sass::vector children; + children.insert(children.begin(), + compound->begin(), compound->begin() + i); + // std::cerr << "Options Add 2\n"; + Extender sel = extenderForCompound(SASS_MEMORY_NEW( + CompoundSelector, compound->pstate(), std::move(children)), + {}, component->combinators()); + //std::cerr << "extender for compound [" << sel.selector->inspect() << "]\n"; + options.push_back({ sel }); } } + //std::cerr << "PUT all extended\n"; + // std::cerr << "Options Add 3\n"; options.insert(options.end(), extended.begin(), extended.end()); } @@ -749,6 +1208,8 @@ namespace Sass { return {}; } + // std::cerr << "OPTIONS " << options.size() << "\n"; + // If [_mode] isn't [ExtendMode.normal] and we didn't use all // the targets in [extensions], extension fails for [compound]. if (targetsUsed != nullptr) { @@ -763,25 +1224,24 @@ namespace Sass { // Optimize for the simple case of a single simple // selector that doesn't need any unification. if (options.size() == 1) { - sass::vector exts = options[0]; + sass::vector& exts = options[0]; for (size_t n = 0; n < exts.size(); n += 1) { - exts[n].assertCompatibleMediaContext(mediaQueryContext, traces); - // To fix invalid css we need to re-order some - // Therefore we need to make copies for them - if (exts[n].extender->isInvalidCss()) { - exts[n].extender = SASS_MEMORY_COPY(exts[n].extender); - for (SelectorComponentObj& component : exts[n].extender->elements()) { - if (CompoundSelector* compound = component->getCompound()) { - if (compound->isInvalidCss()) { - CompoundSelector* copy = SASS_MEMORY_COPY(compound); - copy->sortChildren(); - component = copy; - } - } - } + auto extender = exts[n]; + if (!extender.mediaContext.isNull()) { + SourceSpan span(extender.pstate); + callStackFrame outer(*traces, BackTrace(span, Strings::extendRule)); + callStackFrame inner(*traces, BackTrace(compound->pstate())); + extender.assertCompatibleMediaContext(mediaQueryContext, *traces); } - result.push_back(exts[n].extender); + ComplexSelectorObj complex = extender.selector-> + withAdditionalCombinators(component->combinators()); + //std::cerr << "new complex " << complex->inspect() << "\n"; + if (complex->isUseless()) continue; + result.emplace_back(complex); } + + //std::cerr << "Compound1: " << VecToString2(result) << "\n"; + return result; } @@ -812,167 +1272,222 @@ namespace Sass { bool first = mode != ExtendMode::REPLACE; sass::vector unifiedPaths; - sass::vector> prePaths = permutate(options); + + for (auto qwe2 : options) { + //std::cerr << "options [" << InspectSelector(qwe2) << "]\n"; + } + sass::vector> prePaths = permutate(options); + for (auto qwe2 : prePaths) { + //std::cerr << "prePaths [" << InspectSelector(qwe2) << "]\n"; + } for (size_t i = 0; i < prePaths.size(); i += 1) { - sass::vector> complexes; - const sass::vector& path = prePaths[i]; + sass::vector complexes; + auto& 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]"); + SASS_MEMORY_NEW(CompoundSelector, + compound->pstate()); for (size_t n = 0; n < path.size(); n += 1) { - const ComplexSelectorObj& sel = path[n].extender; - if (CompoundSelectorObj compound = Cast(sel->last())) { + const ComplexSelectorObj& sel = path[n].selector; + if (CompoundSelector* compound = sel->last()->selector()) { mergedSelector->concat(compound->elements()); } } - complexes.push_back({ mergedSelector }); + //std::cerr << "Add initial [" << mergedSelector->inspect() << "]\n"; + complexes.push_back(mergedSelector->wrapInComplex( + {}, component->combinators())); } else { sass::vector originals; - sass::vector> toUnify; + sass::vector toUnify; - for (auto& state : path) { - if (state.isOriginal) { - const ComplexSelectorObj& sel = state.extender; - if (const CompoundSelector* compound = Cast(sel->last())) { + for (auto& extender : path) { + if (extender.isOriginal) { + const ComplexSelectorObj& sel = extender.selector; + if (CompoundSelector* compound = sel->last()->selector()) { originals.insert(originals.end(), compound->last()); } } + else if (extender.selector->isUseless()) { + return {}; + } else { - toUnify.push_back(state.extender->elements()); + //std::cerr << "++ add extender [" << extender.selector->inspect() << "]\n"; + toUnify.emplace_back(extender.selector); } } if (!originals.empty()) { CompoundSelectorObj merged = - SASS_MEMORY_NEW(CompoundSelector, "[compound]"); + SASS_MEMORY_NEW(CompoundSelector, + compound->pstate()); merged->concat(originals); - toUnify.insert(toUnify.begin(), { merged }); + //std::cerr << "++ add first " << VecToString2(originals) << "\n"; + toUnify.insert(toUnify.begin(), { merged->wrapInComplex3() }); + } + //std::cerr << "unifyComplexes: " << VecToString2(toUnify) << "\n"; + auto complexes2 = _unifyComplex(toUnify, compound->pstate()); + for (auto& cplx : complexes2) { + ComplexSelectorObj r = cplx->withAdditionalCombinators(component->combinators()); + if (r->isUseless()) continue; + complexes.push_back(r); } - complexes = unifyComplex(toUnify); + + //std::cerr << "== resulted: " << VecToString2(complexes) << "\n"; if (complexes.empty()) { - return {}; + continue; } } + //std::cerr << "=> result: " << VecToString2(complexes) << "\n"; + + bool lineBreak = false; // var specificity = _sourceSpecificityFor(compound); - for (const Extension& state : path) { - state.assertCompatibleMediaContext(mediaQueryContext, traces); - lineBreak = lineBreak || state.extender->hasPreLineFeed(); + for (auto& state : path) { + if (!state.mediaContext.isNull()) { + SourceSpan span(state.pstate); + callStackFrame outer(*traces, BackTrace(span, Strings::extendRule)); + callStackFrame inner(*traces, BackTrace(compound->pstate())); + state.assertCompatibleMediaContext(mediaQueryContext, *traces); + } + lineBreak = lineBreak || state.selector->hasPreLineFeed(); // specificity = math.max(specificity, state.specificity); } - for (sass::vector& components : complexes) { - auto sel = SASS_MEMORY_NEW(ComplexSelector, "[unified]"); + for (ComplexSelectorObj& sel2 : complexes) { + auto sel = SASS_MEMORY_NEW(ComplexSelector, + compound->pstate(), + sel2->leadingCombinators(), + std::move(sel2->elements())); sel->hasPreLineFeed(lineBreak); - sel->elements(components); - - /* This seems to do too much in regard of previous behavior - for (SelectorComponentObj& component : sel->elements()) { - if (CompoundSelector* compound = component->getCompound()) { - if (compound->isInvalidCss()) { - CompoundSelector* copy = SASS_MEMORY_COPY(compound); - copy->sortChildren(); - component = copy; - } - } - }*/ - - unifiedPaths.push_back(sel); - + unifiedPaths.emplace_back(sel); } } + //std::cerr << "unifiedPaths: " << VecToString2(unifiedPaths) << "\n"; + return unifiedPaths; } - // EO extendCompound + // EO extendCompound (review) - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // Extends [simple] without extending the // contents of any selector pseudos it contains. - // ########################################################################## - sass::vector Extender::extendWithoutPseudo( + ///////////////////////////////////////////////////////////////////////// + sass::vector ExtensionStore::extendWithoutPseudo( const SimpleSelectorObj& simple, const ExtSelExtMap& extensions, ExtSmplSelSet* targetsUsed) const { + auto extensionIt = extensions.find(simple); + if (extensionIt == extensions.end()) return {}; + auto& extensionsForSimple = extensionIt->second; - auto extension = extensions.find(simple); - if (extension == extensions.end()) return {}; - const ExtSelExtMapEntry& extenders = extension->second; - - if (targetsUsed != nullptr) { + if (targetsUsed != nullptr) { targetsUsed->insert(simple); } - if (mode == ExtendMode::REPLACE) { - return extenders.values(); + + sass::vector result; + if (mode != ExtendMode::REPLACE) { + result.emplace_back(extenderForSimple(simple)); } - const sass::vector& - values = extenders.values(); - sass::vector result; - result.reserve(values.size() + 1); - result.push_back(extensionForSimple(simple)); - result.insert(result.end(), values.begin(), values.end()); - return result; + for (auto& ext : extensionsForSimple) { + result.push_back(ext.second->extender); + } + + return std::move(result); } // EO extendWithoutPseudo - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // Extends [simple] and also extending the // contents of any selector pseudos it contains. - // ########################################################################## - sass::vector> Extender::extendSimple( + ///////////////////////////////////////////////////////////////////////// + sass::vector> ExtensionStore::extendSimple( const SimpleSelectorObj& simple, const ExtSelExtMap& extensions, const CssMediaRuleObj& mediaQueryContext, ExtSmplSelSet* targetsUsed) { - if (PseudoSelector* pseudo = Cast(simple)) { + if (PseudoSelector* pseudo = simple->isaPseudoSelector()) { if (pseudo->selector()) { - sass::vector> merged; + sass::vector> merged; sass::vector extended = extendPseudo(pseudo, extensions, mediaQueryContext); for (PseudoSelectorObj& extend : extended) { - SimpleSelectorObj simple = extend; - sass::vector result = + SimpleSelectorObj simple = extend.ptr(); + sass::vector result = extendWithoutPseudo(simple, extensions, targetsUsed); - if (result.empty()) result = { extensionForSimple(extend) }; - merged.push_back(result); + if (result.empty()) result = { extenderForSimple(extend.ptr()) }; + merged.emplace_back(result); } if (!extended.empty()) { return merged; } } } - sass::vector result = + sass::vector result = extendWithoutPseudo(simple, extensions, targetsUsed); + // std::cerr << "Simple " << simple->inspect() << "\n"; + // std::cerr << "Without " << VecToString(result) << "\n"; + // std::cerr << "Exts " << ExtSelToStr(extensions) << "\n"; if (result.empty()) return {}; return { result }; } // extendSimple - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// + // Returns a one-off [Extension] whose extender is composed + // solely of a compound selector containing [simples]. + ///////////////////////////////////////////////////////////////////////// + Extender ExtensionStore::extenderForCompound( + const CompoundSelectorObj& compound, + const SelectorCombinatorVector& prefixes, + const SelectorCombinatorVector& postfixes) const + { + ComplexSelector* complex = compound->wrapInComplex(prefixes, {}); + return Extender{ + complex->pstate(), + complex, maxSourceSpecificity(compound), true }; + } + // EO extenderForCompound + + ///////////////////////////////////////////////////////////////////////// + // Returns a one-off [Extension] whose + // extender is composed solely of [simple]. + ///////////////////////////////////////////////////////////////////////// + Extender ExtensionStore::extenderForSimple( + const SimpleSelectorObj& simple) const + { + ComplexSelector* complex = simple->wrapInComplex({}); + return Extender{ + complex->pstate(), + complex, maxSourceSpecificity(simple), true }; + } + // ExtensionStore::extenderForSimple + + ///////////////////////////////////////////////////////////////////////// // Inner loop helper for [extendPseudo] function - // ########################################################################## - sass::vector Extender::extendPseudoComplex( + ///////////////////////////////////////////////////////////////////////// + sass::vector ExtensionStore::extendPseudoComplex( const ComplexSelectorObj& complex, const PseudoSelectorObj& pseudo, const CssMediaRuleObj& mediaQueryContext) { - if (complex->length() != 1) { return { complex }; } - auto compound = Cast(complex->get(0)); + if (complex->size() != 1) { return { complex }; } + auto compound = complex->get(0)->selector(); if (compound == nullptr) { return { complex }; } - if (compound->length() != 1) { return { complex }; } - auto innerPseudo = Cast(compound->get(0)); + if (compound->size() != 1) { return { complex }; } + auto innerPseudo = compound->get(0)->isaPseudoSelector(); if (innerPseudo == nullptr) { return { complex }; } if (!innerPseudo->selector()) { return { complex }; } @@ -985,15 +1500,17 @@ namespace Sass { // 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 {}; + if (innerPseudo->normalized() != "matches" && + innerPseudo->normalized() != "where" && + innerPseudo->normalized() != "is") return {}; return innerPseudo->selector()->elements(); } - else if (name == "matches" || name == "any" || name == "current" || name == "nth-child" || name == "nth-last-child") { + else if (isSubselectorPseudo(name) || name == "current") { // 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 {}; + if (innerPseudo->argument() != pseudo->argument()) return {}; return innerPseudo->selector()->elements(); } else if (name == "has" || name == "host" || name == "host-context" || name == "slotted") { @@ -1008,35 +1525,38 @@ namespace Sass { } // EO extendPseudoComplex - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // Extends [pseudo] using [extensions], and returns // a list of resulting pseudo selectors. - // ########################################################################## - sass::vector Extender::extendPseudo( + ///////////////////////////////////////////////////////////////////////// + sass::vector ExtensionStore::extendPseudo( const PseudoSelectorObj& 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 {}; } + sass::vector extended; + // Call extend and abort if nothing was extended + if (!pseudo || !pseudo->selector() || + !extendList(pseudo->selector(), extensions, + mediaQueryContext, extended)) { + // Abort pseudo extend + 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. - sass::vector complexes = extended->elements(); + sass::vector complexes = extended; if (pseudo->normalized() == "not") { if (!hasAny(pseudo->selector()->elements(), hasMoreThanOne)) { - if (hasAny(extended->elements(), hasExactlyOne)) { + if (hasAny(extended, hasExactlyOne)) { complexes.clear(); - for (auto& complex : extended->elements()) { - if (complex->length() <= 1) { - complexes.push_back(complex); + for (auto& complex : extended) { + if (complex->size() <= 1) { + complexes.emplace_back(complex); } } } @@ -1044,16 +1564,17 @@ namespace Sass { } sass::vector expanded = expand( - complexes, extendPseudoComplex, pseudo, mediaQueryContext); + std::move(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) { + if (pseudo->selector()->size() == 1) { sass::vector pseudos; for (size_t i = 0; i < expanded.size(); i += 1) { - pseudos.push_back(pseudo->withSelector( + pseudos.emplace_back(pseudo->withSelector( expanded[i]->wrapInList() )); } @@ -1061,40 +1582,23 @@ namespace Sass { } } - SelectorListObj list = SASS_MEMORY_NEW(SelectorList, "[pseudo]"); - list->concat(expanded); + SelectorListObj list = SASS_MEMORY_NEW(SelectorList, + pseudo->pstate(), std::move(expanded)); return { pseudo->withSelector(list) }; } // EO extendPseudo - // ########################################################################## - // Rotates the element in list from [start] (inclusive) to [end] (exclusive) - // one index higher, looping the final element back to [start]. - // ########################################################################## - void Extender::rotateSlice( - sass::vector& list, - size_t start, size_t end) - { - 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 - // ########################################################################## - sass::vector Extender::trim( - const sass::vector& selectors, + ///////////////////////////////////////////////////////////////////////// + void ExtensionStore::trim( + sass::vector& selectors, const ExtCplxSelSet& existing) const { @@ -1103,7 +1607,7 @@ namespace Sass { // 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 performs in C++ (up the limit) - if (selectors.size() > 100) return selectors; + if (selectors.size() > 100) return; // 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 @@ -1112,7 +1616,7 @@ namespace Sass { size_t i = selectors.size(); outer: // Use label to continue loop - while (--i != sass::string::npos) { + while (--i != std::string::npos) { const ComplexSelectorObj& complex1 = selectors[i]; // Check if selector in known in existing "originals" @@ -1136,8 +1640,8 @@ namespace Sass { // 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)) { + for (const CplxSelComponentObj& component : complex1->elements()) { + if (const CompoundSelectorObj compound = component->selector()) { maxSpecificity = std::max(maxSpecificity, maxSourceSpecificity(compound)); } } @@ -1162,15 +1666,15 @@ namespace Sass { } - return result; + selectors = std::move(result); } // EO trim - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // Returns the maximum specificity of the given [simple] source selector. - // ########################################################################## - size_t Extender::maxSourceSpecificity(const SimpleSelectorObj& simple) const + ///////////////////////////////////////////////////////////////////////// + size_t ExtensionStore::maxSourceSpecificity(const SimpleSelectorObj& simple) const { auto it = sourceSpecificity.find(simple); if (it == sourceSpecificity.end()) return 0; @@ -1178,10 +1682,10 @@ namespace Sass { } // EO maxSourceSpecificity(SimpleSelectorObj) - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // Returns the maximum specificity for sources that went into producing [compound]. - // ########################################################################## - size_t Extender::maxSourceSpecificity(const CompoundSelectorObj& compound) const + ///////////////////////////////////////////////////////////////////////// + size_t ExtensionStore::maxSourceSpecificity(const CompoundSelectorObj& compound) const { size_t specificity = 0; for (auto simple : compound->elements()) { @@ -1192,34 +1696,52 @@ namespace Sass { } // EO maxSourceSpecificity(CompoundSelectorObj) - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // Helper function used as callbacks on lists - // ########################################################################## - bool Extender::dontTrimComplex( + ///////////////////////////////////////////////////////////////////////// + bool ExtensionStore::dontTrimComplex( const ComplexSelector* complex2, const ComplexSelector* complex1, const size_t maxSpecificity) { - if (complex2->minSpecificity() < maxSpecificity) return false; - return complex2->isSuperselectorOf(complex1); + // std::cerr << "IS " << complex2->isSuperselectorOf(complex1) << "\n"; + return complex2->minSpecificity() >= maxSpecificity && + complex2->isSuperselectorOf(complex1); } // EO dontTrimComplex - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// + // Rotates the element in list from [start] (inclusive) to [end] (exclusive) + // one index higher, looping the final element back to [start]. + ///////////////////////////////////////////////////////////////////////// + void ExtensionStore::rotateSlice( + sass::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 + + ///////////////////////////////////////////////////////////////////////// // Helper function used as callbacks on lists - // ########################################################################## - bool Extender::hasExactlyOne(const ComplexSelectorObj& vec) + ///////////////////////////////////////////////////////////////////////// + bool ExtensionStore::hasExactlyOne(const ComplexSelectorObj& vec) { - return vec->length() == 1; + return vec->size() == 1; } // EO hasExactlyOne - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // Helper function used as callbacks on lists - // ########################################################################## - bool Extender::hasMoreThanOne(const ComplexSelectorObj& vec) + ///////////////////////////////////////////////////////////////////////// + bool ExtensionStore::hasMoreThanOne(const ComplexSelectorObj& vec) { - return vec->length() > 1; + return vec->size() > 1; } // hasMoreThanOne diff --git a/src/extender.hpp b/src/extender.hpp index 55d6d62340..8a9be0d9f7 100644 --- a/src/extender.hpp +++ b/src/extender.hpp @@ -1,182 +1,217 @@ -#ifndef SASS_EXTENDER_H -#define SASS_EXTENDER_H +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#ifndef SASS_EXTENDER_HPP +#define SASS_EXTENDER_HPP + +// sass.hpp must go before all system headers +// to get the __EXTENSIONS__ fix on Solaris. +#include "capi_sass.hpp" #include #include -#include #include "ast_helpers.hpp" -#include "ast_fwd_decl.hpp" -#include "operation.hpp" -#include "extension.hpp" #include "backtrace.hpp" -#include "ordered_map.hpp" +#include "extension.hpp" namespace Sass { - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // Different hash map types used by extender - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// + + // This is special (ptrs!) + typedef UnorderedSet< + ExtensionObj, + ObjPtrHash, + ObjPtrEquality + > ExtSet; // This is special (ptrs!) - typedef std::unordered_set< + typedef UnorderedSet< ComplexSelectorObj, ObjPtrHash, ObjPtrEquality > ExtCplxSelSet; - typedef std::unordered_set< + typedef UnorderedSet< SimpleSelectorObj, ObjHash, - ObjEquality + ObjEquality, + Sass::Allocator > ExtSmplSelSet; - typedef std::unordered_set< + // This is a very busy set + typedef UnorderedSet< SelectorListObj, ObjPtrHash, - ObjPtrEquality + ObjPtrEquality, + Sass::Allocator > ExtListSelSet; - typedef std::unordered_map< + // This is a very busy map + typedef UnorderedMap< SimpleSelectorObj, ExtListSelSet, ObjHash, ObjEquality + // , Sass::Allocator > ExtSelMap; - typedef ordered_map< + typedef OrderedMap< ComplexSelectorObj, - Extension, + ExtensionObj, ObjHash, - ObjEquality + ObjEquality, + Sass::Allocator> > ExtSelExtMapEntry; - typedef std::unordered_map< + typedef UnorderedMap< SimpleSelectorObj, ExtSelExtMapEntry, ObjHash, - ObjEquality + ObjEquality, + Sass::Allocator> > ExtSelExtMap; - typedef std::unordered_map < + typedef UnorderedMap< SimpleSelectorObj, - sass::vector< - Extension - >, + sass::vector, ObjHash, - ObjEquality + ObjEquality, + Sass::Allocator>> > ExtByExtMap; - - class Extender : public Operation_CRTP { + + class ExtensionStore : public RefCounted { public: enum ExtendMode { TARGETS, REPLACE, NORMAL, }; + mutable ExtSmplSelSet wasExtended2; + private: - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // The mode that controls this extender's behavior. - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// ExtendMode mode; - // ########################################################################## - // Shared backtraces with context and expander. Needed the throw + ///////////////////////////////////////////////////////////////////////// + // Shared back-traces with context and expander. Needed the throw // errors when e.g. extending across media query boundaries. - // ########################################################################## - Backtraces& traces; + ///////////////////////////////////////////////////////////////////////// + BackTraces* traces = nullptr; - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // 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; + ///////////////////////////////////////////////////////////////////////// + public: + ExtSelMap selectors54; - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // A map from all extended simple selectors // to the sources of those extensions. - // ########################################################################## - ExtSelExtMap extensions; + ///////////////////////////////////////////////////////////////////////// + ExtSelExtMap extensionsBySimpleSelector; - // ########################################################################## + /// Whether this extender has no extensions. + bool isEmpty() const { + // Simply check if anything was registered + return extensionsBySimpleSelector.empty(); + } + + sass::string toString(); + + ///////////////////////////////////////////////////////////////////////// // A map from all simple selectors in extenders to // the extensions that those extenders define. - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// ExtByExtMap extensionsByExtender; - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // A map from CSS rules to the media query contexts they're defined in. // This tracks the contexts in which each style rule is defined. // If a rule is defined at the top level, it doesn't have an entry. - // ########################################################################## - ordered_map< + ///////////////////////////////////////////////////////////////////////// + OrderedMap< SelectorListObj, CssMediaRuleObj, ObjPtrHash, - ObjPtrEquality + ObjPtrEquality, + Sass::Allocator> > mediaContexts; - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // A map from [SimpleSelector]s to the specificity of their source selectors. // This tracks the maximum specificity of the [ComplexSelector] that originally // contained each [SimpleSelector]. This allows us to ensure we don't trim any // selectors that need to exist to satisfy the [second law that of extend][]. // [second law of extend]: https://github.com/sass/sass/issues/324#issuecomment-4607184 - // ########################################################################## - std::unordered_map< + ///////////////////////////////////////////////////////////////////////// + UnorderedMap< SimpleSelectorObj, size_t, ObjPtrHash, - ObjPtrEquality + ObjPtrEquality, + Sass::Allocator> > sourceSpecificity; - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // A set of [ComplexSelector]s that were originally part of their // component [SelectorList]s, as opposed to being added by `@extend`. // This allows us to ensure that we don't trim any selectors // that need to exist to satisfy the [first law of extend][]. - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// ExtCplxSelSet originals; public: - // Constructor without default [mode]. - // [traces] are needed to throw errors. - Extender(Backtraces& traces); - - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // Constructor with specific [mode]. // [traces] are needed to throw errors. - // ########################################################################## - Extender(ExtendMode mode, Backtraces& traces); + ///////////////////////////////////////////////////////////////////////// + ExtensionStore(ExtendMode mode, BackTraces& traces); + ExtensionStore(); - // ########################################################################## + void addNonOriginalSelectors(ExtSmplSelSet originalSelectors, ExtSet& unsatisfiedExtensions); + void delNonOriginalSelectors(ExtSmplSelSet originalSelectors, ExtSet& unsatisfiedExtensions); + + ///////////////////////////////////////////////////////////////////////// // Empty desctructor - // ########################################################################## - ~Extender() {}; + ///////////////////////////////////////////////////////////////////////// + // ~ExtensionStore() {}; - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // 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& selector, const SelectorListObj& source, const SelectorListObj& target, - Backtraces& traces); + Logger& logger); - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // Returns a copy of [selector] with [targets] replaced by [source]. - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// static SelectorListObj replace( - SelectorListObj& selector, + const SelectorListObj& selector, const SelectorListObj& source, const SelectorListObj& target, - Backtraces& traces); + Logger& logger); - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // Adds [selector] to this extender, with [selectorSpan] as the span covering // the selector and [ruleSpan] as the span covering the entire style rule. // Extends [selector] using any registered extensions, then returns an empty @@ -184,211 +219,226 @@ namespace Sass { // extensions are added, the returned rule is automatically updated. // The [mediaContext] is the media query context in which the selector was // defined, or `null` if it was defined at the top level of the document. - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// void addSelector( const SelectorListObj& selector, const CssMediaRuleObj& mediaContext); - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // Registers the [SimpleSelector]s in [list] // to point to [rule] in [selectors]. - // ########################################################################## - void registerSelector( + ///////////////////////////////////////////////////////////////////////// + void _registerSelector( const SelectorListObj& list, - const SelectorListObj& rule); + const SelectorListObj& rule, + bool onlyPublic = false); - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // 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, + const ExtendRuleObj& extend, 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( + ///////////////////////////////////////////////////////////////////////// + bool checkForUnsatisfiedExtends2( Extension& unsatisfied) const; + ///////////////////////////////////////////////////////////////////////// + /// Extends [this] with all the extensions in [extensions]. + /// These extensions will extend all selectors already in [this], + /// but they will *not* extend other extensions from [extenders]. + ///////////////////////////////////////////////////////////////////////// + void addExtensions( + sass::vector& extensionStores); + + private: - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // A helper function for [extend] and [replace]. - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// static SelectorListObj extendOrReplace( - SelectorListObj& selector, + const SelectorListObj& selector, const SelectorListObj& source, const SelectorListObj& target, const ExtendMode mode, - Backtraces& traces); + Logger& logger); - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // 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); + ///////////////////////////////////////////////////////////////////////// + static Extension* mergeExtension( + Extension* lhs, + Extension* rhs); - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // Extend [extensions] using [newExtensions]. - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // Note: dart-sass throws an error in here - // ########################################################################## - void extendExistingStyleRules( + ///////////////////////////////////////////////////////////////////////// + void _extendExistingSelectors( 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( + ///////////////////////////////////////////////////////////////////////// + ExtSelExtMap _extendExistingExtensions( // was ExtSelExtMap // Taking in a reference here makes MSVC debug stuck!? - const sass::vector& extensions, + const sass::vector& extensions, const ExtSelExtMap& newExtensions); - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // Extends [list] using [extensions]. - // ########################################################################## - SelectorListObj extendList( + ///////////////////////////////////////////////////////////////////////// + bool extendList( const SelectorListObj& list, const ExtSelExtMap& extensions, - const CssMediaRuleObj& mediaContext); + const CssMediaRuleObj& mediaContext, + sass::vector& result); - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // Extends [complex] using [extensions], and // returns the contents of a [SelectorList]. - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// sass::vector extendComplex( // Taking in a reference here makes MSVC debug stuck!? const ComplexSelectorObj& list, const ExtSelExtMap& extensions, const CssMediaRuleObj& mediaQueryContext); - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // Returns a one-off [Extension] whose // extender is composed solely of [simple]. - // ########################################################################## - Extension extensionForSimple( + ///////////////////////////////////////////////////////////////////////// + Extender extenderForSimple( const SimpleSelectorObj& simple) const; - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // Returns a one-off [Extension] whose extender is composed // solely of a compound selector containing [simples]. - // ########################################################################## - Extension extensionForCompound( + ///////////////////////////////////////////////////////////////////////// + Extender extenderForCompound( // Taking in a reference here makes MSVC debug stuck!? - const sass::vector& simples) const; + const CompoundSelectorObj& compound, + const SelectorCombinatorVector& prefixes, + const SelectorCombinatorVector& postfixes) const; - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // Extends [compound] using [extensions], and returns the // contents of a [SelectorList]. The [inOriginal] parameter // indicates whether this is in an original complex selector, // meaning that [compound] should not be trimmed out. - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// sass::vector extendCompound( - const CompoundSelectorObj& compound, + const CplxSelComponentObj& component, const ExtSelExtMap& extensions, const CssMediaRuleObj& mediaQueryContext, + const SelectorCombinatorVector& prefixes, bool inOriginal = false); - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // Extends [simple] without extending the // contents of any selector pseudos it contains. - // ########################################################################## - sass::vector extendWithoutPseudo( + ///////////////////////////////////////////////////////////////////////// + sass::vector extendWithoutPseudo( const SimpleSelectorObj& simple, const ExtSelExtMap& extensions, ExtSmplSelSet* targetsUsed) const; - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // Extends [simple] and also extending the // contents of any selector pseudos it contains. - // ########################################################################## - sass::vector> extendSimple( + ///////////////////////////////////////////////////////////////////////// + sass::vector> extendSimple( const SimpleSelectorObj& simple, const ExtSelExtMap& extensions, const CssMediaRuleObj& mediaQueryContext, ExtSmplSelSet* targetsUsed); - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // Inner loop helper for [extendPseudo] function - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// static sass::vector extendPseudoComplex( const ComplexSelectorObj& complex, const PseudoSelectorObj& pseudo, const CssMediaRuleObj& mediaQueryContext); - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // Extends [pseudo] using [extensions], and returns // a list of resulting pseudo selectors. - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// sass::vector extendPseudo( const PseudoSelectorObj& pseudo, const ExtSelExtMap& extensions, const CssMediaRuleObj& mediaQueryContext); - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // Rotates the element in list from [start] (inclusive) to [end] (exclusive) // one index higher, looping the final element back to [start]. - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// static void rotateSlice( sass::vector& list, size_t start, size_t end); - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // Removes elements from [selectors] if they're subselectors of other // elements. The [isOriginal] callback indicates which selectors are // original to the document, and thus should never be trimmed. - // ########################################################################## - sass::vector trim( - const sass::vector& selectors, + ///////////////////////////////////////////////////////////////////////// + void trim( + sass::vector& selectors, const ExtCplxSelSet& set) const; - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // Returns the maximum specificity of the given [simple] source selector. - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// size_t maxSourceSpecificity(const SimpleSelectorObj& simple) const; - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // Returns the maximum specificity for sources that went into producing [compound]. - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// size_t maxSourceSpecificity(const CompoundSelectorObj& compound) const; - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // Helper function used as callbacks on lists - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// static bool dontTrimComplex( const ComplexSelector* complex2, const ComplexSelector* complex1, const size_t maxSpecificity); - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // Helper function used as callbacks on lists - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// static bool hasExactlyOne(const ComplexSelectorObj& vec); static bool hasMoreThanOne(const ComplexSelectorObj& vec); @@ -396,4 +446,6 @@ namespace Sass { } +#include "extension.hpp" + #endif diff --git a/src/extension.cpp b/src/extension.cpp index 80f5f41303..19a5ffe3ff 100644 --- a/src/extension.cpp +++ b/src/extension.cpp @@ -1,43 +1,122 @@ -// sass.hpp must go before all system headers to get the -// __EXTENSIONS__ fix on Solaris. -#include "sass.hpp" +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#include "extension.hpp" +#include "callstack.hpp" #include "ast_helpers.hpp" -#include "extension.hpp" -#include "ast.hpp" +#include "exceptions.hpp" +#include "ast_css.hpp" namespace Sass { - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // Static function to create a copy with a new extender - // ########################################################################## - Extension Extension::withExtender(const ComplexSelectorObj& newExtender) const + ///////////////////////////////////////////////////////////////////////// + Extension* Extension::withExtender(ComplexSelectorObj& newExtender) const + { + return SASS_MEMORY_NEW(Extension, + newExtender->pstate(), + newExtender, target, mediaContext, isOptional); + } + + + // 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::Extension( + const SourceSpan& pstate, + ComplexSelectorObj& extender, + const SimpleSelectorObj& target, + const CssMediaRuleObj& mediaContext, + bool isOriginal, bool isOptional) : + pstate(pstate), + extender(pstate, extender, 0, isOriginal, mediaContext), + target(target), + specificity(extender->maxSpecificity()), + isOptional(isOptional), + isOriginal(isOriginal), + isConsumed(false), + mediaContext(mediaContext) + { + } + + +// Extension::Extension(Extender extender) : +// extender(extender), +// specificity(0), +// isOptional(true), +// isOriginal(false), +// isConsumed(false) +// {} +// + // Copy constructor + + Extension::Extension(const Extension & extension) : + extender(extension.extender), + target(extension.target), + specificity(extension.specificity), + isOptional(extension.isOptional), + isOriginal(extension.isOriginal), + isConsumed(extension.isConsumed), + mediaContext(extension.mediaContext) + {} + + Extension::Extension() : + extender(SourceSpan::internal("Ext"), {}, 0, false), + specificity(0), + isOptional(false), + isOriginal(false), + isConsumed(false) + {} + + Extension& Extension::operator=(const Extension& other) { - Extension extension(newExtender); - extension.specificity = specificity; - extension.isOptional = isOptional; - extension.target = target; - return extension; + extender = other.extender; + target = other.target; + specificity = other.specificity; + isOptional = other.isOptional; + isOriginal = other.isOriginal; + isConsumed = other.isConsumed; + mediaContext = other.mediaContext; + return *this; + } + + ///////////////////////////////////////////////////////////////////////// + // 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 && mediaContext == mediaQueryContext) return; + + if (ObjEqualityFn(mediaQueryContext, mediaContext)) return; + + throw Exception::ExtendAcrossMedia(traces, this); + } - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// // Asserts that the [mediaContext] for a selector is // compatible with the query context for this extender. - // ########################################################################## - void Extension::assertCompatibleMediaContext(CssMediaRuleObj mediaQueryContext, Backtraces& traces) const + ///////////////////////////////////////////////////////////////////////// + void Extender::assertCompatibleMediaContext(CssMediaRuleObj mediaQueryContext, BackTraces& traces) const { if (this->mediaContext.isNull()) return; - if (mediaQueryContext && ObjPtrEqualityFn(mediaContext->block(), mediaQueryContext->block())) return; + if (mediaQueryContext && mediaContext == mediaQueryContext) return; if (ObjEqualityFn(mediaQueryContext, mediaContext)) return; - throw Exception::ExtendAcrossMedia(traces, *this); + throw Exception::ExtendAcrossMedia(traces, this); } - // ########################################################################## - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// } diff --git a/src/extension.hpp b/src/extension.hpp index 58fd5f8214..9952f80938 100644 --- a/src/extension.hpp +++ b/src/extension.hpp @@ -1,23 +1,83 @@ -#ifndef SASS_EXTENSION_H -#define SASS_EXTENSION_H +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#ifndef SASS_EXTENSION_HPP +#define SASS_EXTENSION_HPP -// sass.hpp must go before all system headers to get the -// __EXTENSIONS__ fix on Solaris. -#include "sass.hpp" +// sass.hpp must go before all system headers +// to get the __EXTENSIONS__ fix on Solaris. +#include "capi_sass.hpp" -#include -#include #include "ast_fwd_decl.hpp" +#include "ast_selectors.hpp" #include "backtrace.hpp" namespace Sass { - class Extension { + class Extender { + public: + + // The span in which this selector was defined. + SourceSpan pstate; // use from extender + + // The selector in which the `@extend` appeared. + ComplexSelectorObj selector; + + // The minimum specificity required for any + // selector generated from this extender. + size_t specificity; + + // Whether this extender represents a selector that was originally + // in the document, rather than one defined with `@extend`. + bool isOriginal; + + // The extension that created this [Extender]. Not all [Extender]s + // are created by extensions. Some simply represent the + // original selectors that exist in the document. + Extension* extension; + + // The media query context to which this extend is restricted, + // or `null` if it can apply within any context. + CssMediaRuleObj mediaContext; + + // Value constructor + Extender( + const SourceSpan& pstate, + ComplexSelector* extender, + size_t specificity, + bool isOriginal, + CssMediaRuleObj media = {}) : + pstate(pstate), + selector(extender), + specificity(specificity), + isOriginal(isOriginal), + extension(nullptr), + mediaContext(media) + {} + + Extender() : + selector({}), + specificity(0), + isOriginal(false), + extension(nullptr) + {} + + // Asserts that the [mediaContext] for a selector is + // compatible with the query context for this extender. + void assertCompatibleMediaContext(CssMediaRuleObj mediaContext, BackTraces& traces) const; + + }; + + class Extension : public RefCounted { public: + sass::string toString() const; + + SourceSpan pstate; + // The selector in which the `@extend` appeared. - ComplexSelectorObj extender; + Extender extender; // The selector that's being extended. // `null` for one-off extensions. @@ -34,7 +94,8 @@ namespace Sass { // originally in the document, rather than one defined with `@extend`. bool isOriginal; - bool isSatisfied; + // Whether or not this extension was consumed. + bool isConsumed; // The media query context to which this extend is restricted, // or `null` if it can apply within any context. @@ -42,45 +103,28 @@ namespace Sass { // 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({}) { - - } + Extension( + const SourceSpan& pstate, + ComplexSelectorObj& extender, + const SimpleSelectorObj& target, + const CssMediaRuleObj& mediaContext = {}, + bool isOptional = false, + bool isOriginal = true); // 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) { - - } + Extension(const Extension& extension); // Default constructor - Extension() : - extender({}), - target({}), - specificity(0), - isOptional(false), - isOriginal(false), - isSatisfied(false), - mediaContext({}) { - } + Extension(); + + // Copy assignment operator + Extension& operator=(const Extension& other); // Asserts that the [mediaContext] for a selector is // compatible with the query context for this extender. - void assertCompatibleMediaContext(CssMediaRuleObj mediaContext, Backtraces& traces) const; + void assertCompatibleMediaContext(CssMediaRuleObj mediaContext, BackTraces& traces) const; - Extension withExtender(const ComplexSelectorObj& newExtender) const; + Extension* withExtender(ComplexSelectorObj& newExtender) const; }; diff --git a/src/file.cpp b/src/file.cpp index 163abd427b..bdd82e3304 100644 --- a/src/file.cpp +++ b/src/file.cpp @@ -1,11 +1,22 @@ -// sass.hpp must go before all system headers to get the -// __EXTENSIONS__ fix on Solaris. -#include "sass.hpp" +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#include "file.hpp" + +// Some functions are heavily inspired by Perl +// https://perldoc.perl.org/File/Basename.html +// https://perldoc.perl.org/File/Spec.html + +#if defined (_MSC_VER) // Visual studio +#define thread_local __declspec( thread ) +#elif defined (__GCC__) // GCC +#define thread_local __thread +#endif #ifdef _WIN32 # ifdef __MINGW32__ # ifndef off64_t -# define off64_t _off64_t /* Workaround for http://sourceforge.net/p/mingw/bugs/2024/ */ +# define off64_t _off64_t /* Workaround for http://sourceforge.net/p/mingw/bugs/2024/ */ # endif # endif # include @@ -13,91 +24,108 @@ #else # include #endif -#include -#include -#include -#include -#include "file.hpp" -#include "context.hpp" -#include "prelexer.hpp" -#include "utf8_string.hpp" -#include "sass_functions.hpp" -#include "error_handling.hpp" -#include "util.hpp" -#include "util_string.hpp" -#include "sass2scss.h" -#ifdef _WIN32 -# include - -# ifdef _MSC_VER -# include -inline static Sass::sass::string wstring_to_string(const std::wstring& wstr) -{ - std::wstring_convert, wchar_t> wchar_converter; - return wchar_converter.to_bytes(wstr); -} -# else // mingw(/gcc) does not support C++11's codecvt yet. -inline static Sass::sass::string wstring_to_string(const std::wstring &wstr) -{ - int size_needed = WideCharToMultiByte(CP_UTF8, 0, &wstr[0], (int)wstr.size(), NULL, 0, NULL, NULL); - Sass::sass::string strTo(size_needed, 0); - WideCharToMultiByte(CP_UTF8, 0, &wstr[0], (int)wstr.size(), &strTo[0], size_needed, NULL, NULL); - return strTo; -} -# endif -#endif +#include +#include "import.hpp" +#include "sources.hpp" +#include "unicode.hpp" +#include "character.hpp" +#include "exceptions.hpp" +#include "string_utils.hpp" namespace Sass { - namespace File { - // return the current directory - // always with forward slashes - // always with trailing slash - sass::string get_cwd() - { - const size_t wd_len = 4096; - #ifndef _WIN32 - char wd[wd_len]; - char* pwd = getcwd(wd, wd_len); - // we should check error for more detailed info (e.g. ENOENT) - // http://man7.org/linux/man-pages/man2/getcwd.2.html#ERRORS - if (pwd == NULL) throw Exception::OperationError("cwd gone missing"); - sass::string cwd = pwd; - #else - wchar_t wd[wd_len]; - wchar_t* pwd = _wgetcwd(wd, wd_len); - if (pwd == NULL) throw Exception::OperationError("cwd gone missing"); - sass::string cwd = wstring_to_string(pwd); - //convert backslashes to forward slashes - replace(cwd.begin(), cwd.end(), '\\', '/'); - #endif - if (cwd[cwd.length() - 1] != '/') cwd += '/'; - return cwd; + // return the current directory + // always with forward slashes + // always with trailing slash + extern sass::string get_pwd() + { + const size_t wd_len = 4096; + #ifndef _WIN32 + char wd[wd_len]; + char* pwd = getcwd(wd, wd_len); + // we should check error for more detailed info (e.g. ENOENT) + // http://man7.org/linux/man-pages/man2/getcwd.2.html#ERRORS + // Cwd may return null if we are in a directory that has been deleted + if (pwd == NULL) throw Exception::OperationError("cwd gone missing"); + sass::string cwd = pwd; + #else + wchar_t wd[wd_len]; + wchar_t* pwd = _wgetcwd(wd, wd_len); + // Cwd may return null if we are in a directory that has been deleted + if (pwd == NULL) throw Exception::OperationError("cwd gone missing"); + // Windows always returns utf16, convert to utf8 + sass::string cwd = Unicode::utf16to8(pwd); + //convert backslashes to forward slashes + replace(cwd.begin(), cwd.end(), '\\', '/'); + #endif + if (cwd[cwd.length() - 1] != '/') cwd += '/'; + return cwd; + } + // EO extern get_pwd + + // Use heap pointer to avoid some compiler issues + // Has proven to be the most stable, but the memory + // will kinda leak (not relevant since long living). + // One known offender is mingw v8.1.0 x86/i686 + static thread_local sass::string* cwd; + + // Initialize current directory once + extern void set_cwd(const sass::string& path) + { + if (cwd == nullptr) { + // Create object on the heap + cwd = new sass::string(get_pwd()); } + // Assign to heap object + *cwd = path; + } + // EO extern set_cwd + + // Initialize current directory once + extern const sass::string& CWD() { + if (cwd == nullptr) { + cwd = new sass::string(get_pwd()); + } + return *cwd; + } + // EO extern CWD + + namespace File { // test if path exists and is a file - bool file_exists(const sass::string& path) + // takes optional cache map to improve performance + bool file_exists(const sass::string& path, const sass::string& CWD, std::unordered_map& cache) { #ifdef _WIN32 wchar_t resolved[32768]; - // windows unicode filepaths are encoded in utf16 - sass::string abspath(join_paths(get_cwd(), path)); + // windows unicode file-paths are encoded in utf16 + sass::string abspath(join_paths(CWD, path)); if (!(abspath[0] == '/' && abspath[1] == '/')) { abspath = "//?/" + abspath; } - std::wstring wpath(UTF_8::convert_to_utf16(abspath)); + auto it = cache.find(abspath); + if (it != cache.end()) { + return it->second; + } + sass::wstring wpath(Unicode::utf8to16(abspath)); std::replace(wpath.begin(), wpath.end(), '/', '\\'); DWORD rv = GetFullPathNameW(wpath.c_str(), 32767, resolved, NULL); if (rv > 32767) throw Exception::OperationError("Path is too long"); if (rv == 0) throw Exception::OperationError("Path could not be resolved"); - DWORD dwAttrib = GetFileAttributesW(resolved); - return (dwAttrib != INVALID_FILE_ATTRIBUTES && - (!(dwAttrib & FILE_ATTRIBUTE_DIRECTORY))); + DWORD dwAttrib = GetFileAttributesW(resolved); // was 3% + bool result = (dwAttrib != INVALID_FILE_ATTRIBUTES + && (!(dwAttrib & FILE_ATTRIBUTE_DIRECTORY))); + cache[abspath] = result; + return result; #else struct stat st_buf; - return (stat (path.c_str(), &st_buf) == 0) && - (!S_ISDIR (st_buf.st_mode)); + sass::string abspath(join_paths(CWD, path)); + // euidaccess might be faster + bool result = (stat (abspath.c_str(), &st_buf) == 0) + && (!S_ISDIR (st_buf.st_mode)); + cache[abspath] = result; + return result; #endif } @@ -106,17 +134,18 @@ namespace Sass { bool is_absolute_path(const sass::string& path) { #ifdef _WIN32 - if (path.length() >= 2 && Util::ascii_isalpha(path[0]) && path[1] == ':') return true; + if (path.length() >= 3 && Character::isAlphabetic(path[0]) && path[1] == ':') return true; #endif size_t i = 0; // check if we have a protocol - if (path[i] && Util::ascii_isalpha(static_cast(path[i]))) { + if (path[i] && Character::isAlphabetic(static_cast(path[i]))) { // skip over all alphanumeric characters - while (path[i] && Util::ascii_isalnum(static_cast(path[i]))) ++i; + while (path[i] && Character::isAlphanumeric(static_cast(path[i]))) ++i; i = i && path[i] == ':' ? i + 1 : 0; } return path[i] == '/'; } + // EO is_absolute_path // helper function to find the last directory separator inline size_t find_last_folder_separator(const sass::string& path, size_t limit = sass::string::npos) @@ -139,6 +168,7 @@ namespace Sass { } return pos; } + // EO find_last_folder_separator // return only the directory part of path sass::string dir_name(const sass::string& path) @@ -147,6 +177,7 @@ namespace Sass { if (pos == sass::string::npos) return ""; else return path.substr(0, pos+1); } + // EO dir_name // return only the filename part of path sass::string base_name(const sass::string& path) @@ -155,9 +186,10 @@ namespace Sass { if (pos == sass::string::npos) return path; else return path.substr(pos+1); } + // EO base_name // do a logical clean up of the path - // no physical check on the filesystem + // no physical check on the file-system sass::string make_canonical_path (sass::string path) { @@ -169,19 +201,18 @@ namespace Sass { replace(path.begin(), path.end(), '\\', '/'); #endif - pos = 0; // remove all self references inside the path string + pos = 0; // remove all self references inside the path string (`/./`) while((pos = path.find("/./", pos)) != sass::string::npos) path.erase(pos, 2); // remove all leading and trailing self references while(path.size() >= 2 && path[0] == '.' && path[1] == '/') path.erase(0, 2); while((pos = path.length()) > 1 && path[pos - 2] == '/' && path[pos - 1] == '.') path.erase(pos - 2); - size_t proto = 0; // check if we have a protocol - if (path[proto] && Util::ascii_isalpha(static_cast(path[proto]))) { + if (path[proto] && Character::isAlphabetic(path[proto])) { // skip over all alphanumeric characters - while (path[proto] && Util::ascii_isalnum(static_cast(path[proto++]))) {} + while (path[proto] && Character::isAlphanumeric(path[proto++])) {} // then skip over the mandatory colon if (proto && path[proto] == ':') ++ proto; } @@ -195,9 +226,12 @@ namespace Sass { return path; } + // EO make_canonical_path // join two path segments cleanly together // but only if right side is not absolute yet + // Can we avoid the two string copies? + // OK, one copy is needed anyway sass::string join_paths(sass::string l, sass::string r) { @@ -231,46 +265,28 @@ namespace Sass { return l + r; } - - sass::string path_for_console(const sass::string& rel_path, const sass::string& abs_path, const sass::string& orig_path) - { - // magic algorithm goes here!! - - // if the file is outside this directory show the absolute path - if (rel_path.substr(0, 3) == "../") { - return orig_path; - } - // this seems to work most of the time - return abs_path == orig_path ? abs_path : rel_path; - } + // EO join_paths // create an absolute path by resolving relative paths with cwd - sass::string rel2abs(const sass::string& path, const sass::string& base, const sass::string& cwd) + sass::string rel2abs(const sass::string& path, const sass::string& base, const sass::string& CWD) { - sass::string rv = make_canonical_path(join_paths(join_paths(cwd + "/", base + "/"), path)); - #ifdef _WIN32 - // On windows we may get an absolute path without directory - // In that case we should prepend the directory from the root - if (rv[0] == '/' && rv[1] != '/') { - rv.insert(0, cwd, 0, 2); - } - #endif - return rv; + return make_canonical_path(join_paths(join_paths(CWD + "/", base + "/"), path)); } + // EO rel2abs // create a path that is relative to the given base directory // path and base will first be resolved against cwd to make them absolute - sass::string abs2rel(const sass::string& path, const sass::string& base, const sass::string& cwd) + sass::string abs2rel(const sass::string& path, const sass::string& base, const sass::string& CWD) { - sass::string abs_path = rel2abs(path, cwd); - sass::string abs_base = rel2abs(base, cwd); + sass::string abs_path = rel2abs(path, CWD, CWD); + sass::string abs_base = rel2abs(base, CWD, CWD); size_t proto = 0; // check if we have a protocol - if (path[proto] && Util::ascii_isalpha(static_cast(path[proto]))) { + if (path[proto] && Character::isAlphabetic(static_cast(path[proto]))) { // skip over all alphanumeric characters - while (path[proto] && Util::ascii_isalnum(static_cast(path[proto++]))) {} + while (path[proto] && Character::isAlphanumeric(static_cast(path[proto++]))) {} // then skip over the mandatory colon if (proto && path[proto] == ':') ++ proto; } @@ -291,13 +307,12 @@ namespace Sass { size_t index = 0; size_t minSize = std::min(abs_path.size(), abs_base.size()); for (size_t i = 0; i < minSize; ++i) { - #ifdef FS_CASE_SENSITIVE + #ifdef FS_CASE_SENSITIVITY if (abs_path[i] != abs_base[i]) break; #else - // compare the charactes in a case insensitive manner - // windows fs is only case insensitive in ascii ranges - if (Util::ascii_tolower(static_cast(abs_path[i])) != - Util::ascii_tolower(static_cast(abs_base[i]))) break; + // compare the characters in a case insensitive manner + // windows FS is only case insensitive in ASCII ranges + if (!Character::characterEqualsIgnoreCase(abs_path[i], abs_base[i])) break; #endif if (abs_path[i] == '/') index = i + 1; } @@ -333,6 +348,7 @@ namespace Sass { return result; } + // EO abs2rel // Resolution order for ambiguous imports: // (1) filename as given @@ -341,96 +357,140 @@ namespace Sass { // (4) given + extension // (5) given + _index.scss // (6) given + _index.sass - sass::vector resolve_includes(const sass::string& root, const sass::string& file, const sass::vector& exts) + void find_file_or_partial( + const sass::string& root, + const sass::string& dirname, + const sass::string& basename, + const sass::string& suffix, + const sass::string& CWD, + bool considerImports, + std::unordered_map& cache, + const std::vector& exts, + sass::vector& candidates) + { + sass::string relPath; + sass::string absPath; + + if (considerImports) { + find_file_or_partial(root, dirname, basename + ".import", + ".sass", CWD, false, cache, {}, candidates); + find_file_or_partial(root, dirname, basename + ".import", + ".scss", CWD, false, cache, {}, candidates); + if (candidates.size()) return; + } + + if (basename[0] != '_') { + relPath = join_paths(dirname, "_" + basename + suffix); + absPath = join_paths(root, relPath); + if (file_exists(join_paths(root, relPath), CWD, cache)) { + ImportRequest request(relPath, root, considerImports); + ResolvedImport import(request, absPath, SASS_IMPORT_AUTO); + candidates.push_back(import); + } + } + relPath = join_paths(dirname, basename + suffix); + absPath = join_paths(root, relPath); + if (file_exists(join_paths(root, relPath), CWD, cache)) { + ImportRequest request(relPath, root, considerImports); + ResolvedImport import(request, absPath, SASS_IMPORT_AUTO); + candidates.push_back(import); + } + + // Don't look for any other suffixes, we already got one! + if (!suffix.empty()) return; + + for (auto ext : exts) { + if (ext == ".css" && candidates.size()) return; + if (basename[0] != '_') { + relPath = join_paths(dirname, "_" + basename + suffix + ext); + absPath = join_paths(root, relPath); + if (file_exists(join_paths(root, relPath), CWD, cache)) { + ImportRequest request(relPath, root, considerImports); + ResolvedImport import(request, absPath, SASS_IMPORT_AUTO); + candidates.push_back(import); + } + } + relPath = join_paths(dirname, basename + suffix + ext); + absPath = join_paths(root, relPath); + if (file_exists(join_paths(root, relPath), CWD, cache)) { + ImportRequest request(relPath, root, considerImports); + ResolvedImport import(request, absPath, SASS_IMPORT_AUTO); + candidates.push_back(import); + } + } + } + + // Resolution order for ambiguous imports: + // (1) filename as given + // (2) underscore + given + // (3) underscore + given + extension + // (4) given + extension + // (5) given + _index.scss + // (6) given + _index.sass + sass::vector resolve_includes( + const sass::string& root, + const sass::string& file, + const sass::string& CWD, + bool forImport, + std::unordered_map& cache, + const std::vector& exts) { sass::string filename = join_paths(root, file); // split the filename sass::string base(dir_name(file)); sass::string name(base_name(file)); - sass::vector includes; + sass::vector includes; // create full path (maybe relative) sass::string rel_path(join_paths(base, name)); sass::string abs_path(join_paths(root, rel_path)); - if (file_exists(abs_path)) includes.push_back({{ rel_path, root }, abs_path }); - // next test variation with underscore - rel_path = join_paths(base, "_" + name); - abs_path = join_paths(root, rel_path); - if (file_exists(abs_path)) includes.push_back({{ rel_path, root }, abs_path }); - // next test exts plus underscore - for(auto ext : exts) { - rel_path = join_paths(base, "_" + name + ext); - abs_path = join_paths(root, rel_path); - if (file_exists(abs_path)) includes.push_back({{ rel_path, root }, abs_path }); - } - // next test plain name with exts - for(auto ext : exts) { - rel_path = join_paths(base, name + ext); - abs_path = join_paths(root, rel_path); - if (file_exists(abs_path)) includes.push_back({{ rel_path, root }, abs_path }); - } - // index files - if (includes.size() == 0) { - // ignore directories that look like @import'able filename - for(auto ext : exts) { - if (ends_with(name, ext)) return includes; - } - // next test underscore index exts - for(auto ext : exts) { - rel_path = join_paths(base, join_paths(name, "_index" + ext)); - abs_path = join_paths(root, rel_path); - if (file_exists(abs_path)) includes.push_back({{ rel_path, root }, abs_path }); - } - // next test plain index exts - for(auto ext : exts) { - rel_path = join_paths(base, join_paths(name, "index" + ext)); - abs_path = join_paths(root, rel_path); - if (file_exists(abs_path)) includes.push_back({{ rel_path, root }, abs_path }); + + sass::string suffix; + for (auto ext : exts) { + if (StringUtils::endsWithIgnoreCase(name, ext)) { + name.resize(name.size() - ext.size()); + suffix = ext; + break; } } - // nothing found + + find_file_or_partial(root, base, name, suffix, CWD, forImport, cache, exts, includes); + if (includes.size()) return includes; + sass::string subdir(join_paths(base, name)); + find_file_or_partial(root, subdir, "index", "", CWD, forImport, cache, exts, includes); + if (includes.size()) return includes; + return includes; } + // EO resolve_includes - sass::vector find_files(const sass::string& file, const sass::vector paths) + // Private helper function for find_file + StringVector _find_file(const sass::string& file, const sass::string& CWD, const StringVector paths, std::unordered_map& cache) { - sass::vector includes; - for (sass::string path : paths) { + StringVector includes; + for (const sass::string& path : paths) { sass::string abs_path(join_paths(path, file)); - if (file_exists(abs_path)) includes.push_back(abs_path); + if (file_exists(abs_path, CWD, cache)) includes.emplace_back(abs_path); } return includes; } - - sass::vector find_files(const sass::string& file, struct Sass_Compiler* compiler) - { - // get the last import entry to get current base directory - // struct Sass_Options* options = sass_compiler_get_options(compiler); - Sass_Import_Entry import = sass_compiler_get_last_import(compiler); - const sass::vector& incs = compiler->cpp_ctx->include_paths; - // create the vector with paths to lookup - sass::vector paths(1 + incs.size()); - paths.push_back(dir_name(import->abs_path)); - paths.insert(paths.end(), incs.begin(), incs.end()); - // dispatch to find files in paths - return find_files(file, paths); - } + // EO find_files // helper function to search one file in all include paths // this is normally not used internally by libsass (C-API sugar) - sass::string find_file(const sass::string& file, const sass::vector paths) + sass::string find_file(const sass::string& file, const sass::string& CWD, const StringVector paths, std::unordered_map& cache) { if (file.empty()) return file; - auto res = find_files(file, paths); + auto res = _find_file(file, CWD, paths, cache); return res.empty() ? "" : res.front(); } // helper function to resolve a filename - sass::string find_include(const sass::string& file, const sass::vector paths) + sass::string find_include(const sass::string& file, const sass::string& CWD, const StringVector paths, bool forImport, std::unordered_map& cache) { // search in every include path for a match for (size_t i = 0, S = paths.size(); i < S; ++i) { - sass::vector resolved(resolve_includes(paths[i], file)); + sass::vector resolved(resolve_includes(paths[i], file, CWD, forImport, cache)); if (resolved.size()) return resolved[0].abs_path; } // nothing found @@ -440,35 +500,33 @@ namespace Sass { // try to load the given filename // returned memory must be freed // will auto convert .sass files - char* read_file(const sass::string& path) + char* slurp_file(const sass::string& path, const sass::string& CWD) { #ifdef _WIN32 - BYTE* pBuffer; + char* contents; DWORD dwBytes; wchar_t resolved[32768]; - // windows unicode filepaths are encoded in utf16 - sass::string abspath(join_paths(get_cwd(), path)); + // windows unicode file-paths are encoded in utf16 + sass::string abspath(join_paths(CWD, path)); if (!(abspath[0] == '/' && abspath[1] == '/')) { abspath = "//?/" + abspath; } - std::wstring wpath(UTF_8::convert_to_utf16(abspath)); + sass::wstring wpath(Unicode::utf8to16(abspath)); std::replace(wpath.begin(), wpath.end(), '/', '\\'); DWORD rv = GetFullPathNameW(wpath.c_str(), 32767, resolved, NULL); if (rv > 32767) throw Exception::OperationError("Path is too long"); if (rv == 0) throw Exception::OperationError("Path could not be resolved"); HANDLE hFile = CreateFileW(resolved, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL); if (hFile == INVALID_HANDLE_VALUE) return 0; + // ToDo: do some file locking here!? DWORD dwFileLength = GetFileSize(hFile, NULL); if (dwFileLength == INVALID_FILE_SIZE) return 0; - // allocate an extra byte for the null char - // and another one for edge-cases in lexer - pBuffer = (BYTE*)malloc((dwFileLength+2)*sizeof(BYTE)); - ReadFile(hFile, pBuffer, dwFileLength, &dwBytes, NULL); - pBuffer[dwFileLength+0] = '\0'; - pBuffer[dwFileLength+1] = '\0'; + // allocate an extra byte for the null terminator + contents = (char*) sass_alloc_memory(size_t(dwFileLength) + 1); + if (!ReadFile(hFile, contents, dwFileLength, &dwBytes, NULL)) + throw Exception::OperationError("Could not read file"); + contents[dwFileLength] = '\0'; // ensure null terminator CloseHandle(hFile); - // just convert from unsigned char* - char* contents = (char*) pBuffer; #else // Read the file using `` instead of `` for better portability. // The `` header initializes `` and this buggy in GCC4/5 with static linking. @@ -480,7 +538,7 @@ namespace Sass { FILE* fd = std::fopen(path.c_str(), "rb"); if (fd == nullptr) return nullptr; const std::size_t size = st.st_size; - char* contents = static_cast(malloc(st.st_size + 2 * sizeof(char))); + char* contents = static_cast(sass_alloc_memory(st.st_size + 1 * sizeof(char))); if (std::fread(static_cast(contents), 1, size, fd) != size) { free(contents); std::fclose(fd); @@ -491,41 +549,32 @@ namespace Sass { return nullptr; } contents[size] = '\0'; - contents[size + 1] = '\0'; #endif - sass::string extension; - if (path.length() > 5) { - extension = path.substr(path.length() - 5, 5); - } - Util::ascii_str_tolower(&extension); - if (extension == ".sass" && contents != 0) { - char * converted = sass2scss(contents, SASS2SCSS_PRETTIFY_1 | SASS2SCSS_KEEP_COMMENT); - free(contents); // free the indented contents - return converted; // should be freed by caller - } else { - return contents; - } + return contents; } + // EO slurp_file - // split a path string delimited by semicolons or colons (OS dependent) - sass::vector split_path_list(const char* str) + // Read and return resolved import + Import* read_import(const ResolvedImport& import) { - sass::vector paths; - if (str == NULL) return paths; - // find delimiter via prelexer (return zero at end) - const char* end = Prelexer::find_first(str); - // search until null delimiter - while (end) { - // add path from current position to delimiter - paths.push_back(sass::string(str, end - str)); - str = end + 1; // skip delimiter - end = Prelexer::find_first(str); + // try to read the content of the resolved file entry + // the memory buffer returned to us must be freed by us! + if (char* contents = slurp_file(import.abs_path, CWD())) { + // Return LoadedImport object + // ToDo: Add sourcemap parsing + return SASS_MEMORY_NEW(Import, + SASS_MEMORY_NEW(SourceFile, + import.imp_path.c_str(), + import.abs_path.c_str(), + contents, nullptr + ), import.syntax); } - // add path from current position to end - paths.push_back(sass::string(str)); - // return back - return paths; + // Nothing was found + return nullptr; } + // EO read_import } + // EO File namespace + } diff --git a/src/file.hpp b/src/file.hpp index 78b5244408..02069dbca6 100644 --- a/src/file.hpp +++ b/src/file.hpp @@ -1,26 +1,41 @@ -#ifndef SASS_FILE_H -#define SASS_FILE_H +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#ifndef SASS_FILE_HPP +#define SASS_FILE_HPP -// sass.hpp must go before all system headers to get the -// __EXTENSIONS__ fix on Solaris. -#include "sass.hpp" +// sass.hpp must go before all system headers +// to get the __EXTENSIONS__ fix on Solaris. +#include "capi_sass.hpp" #include #include -#include "sass/context.h" #include "ast_fwd_decl.hpp" +#include "ast_def_macros.hpp" +#include "backtrace.hpp" +#include "hashing.hpp" +#include "source.hpp" namespace Sass { + // return the current directory + // always with forward slashes + extern sass::string get_pwd(); + + // return the current directory + // always with forward slashes + extern void set_cwd(const sass::string& path); + + // Should be thread_local? + extern const sass::string& CWD(); + namespace File { - // return the current directory - // always with forward slashes - sass::string get_cwd(); // test if path exists and is a file - bool file_exists(const sass::string& file); + bool file_exists(const sass::string& file, const sass::string& CWD, + std::unordered_map& cache); // return if given path is absolute // works with *nix and windows paths @@ -32,90 +47,42 @@ namespace Sass { // return only the filename part of path sass::string base_name(const sass::string&); - // do a locigal clean up of the path - // no physical check on the filesystem + // do a logical clean up of the path + // no physical check on the file-system sass::string make_canonical_path (sass::string path); // join two path segments cleanly together // but only if right side is not absolute yet sass::string join_paths(sass::string root, sass::string name); - // if the relative path is outside of the cwd we want want to - // show the absolute path in console messages - sass::string path_for_console(const sass::string& rel_path, const sass::string& abs_path, const sass::string& orig_path); - // create an absolute path by resolving relative paths with cwd - sass::string rel2abs(const sass::string& path, const sass::string& base = ".", const sass::string& cwd = get_cwd()); + sass::string rel2abs(const sass::string& path, const sass::string& base = Sass::CWD(), const sass::string& CWD = Sass::CWD()); // create a path that is relative to the given base directory // path and base will first be resolved against cwd to make them absolute - sass::string abs2rel(const sass::string& path, const sass::string& base = ".", const sass::string& cwd = get_cwd()); + sass::string abs2rel(const sass::string& path, const sass::string& base = Sass::CWD(), const sass::string& CWD = Sass::CWD()); // helper function to resolve a filename // searching without variations in all paths - sass::string find_file(const sass::string& file, struct Sass_Compiler* options); - sass::string find_file(const sass::string& file, const sass::vector paths); + // sass::string find_file(const sass::string& file, const sass::string& CWD, struct SassCompilerCpp* options); + sass::string find_file(const sass::string& file, const sass::string& CWD, const StringVector paths, std::unordered_map& cache); // helper function to resolve a include filename // this has the original resolve logic for sass include - sass::string find_include(const sass::string& file, const sass::vector paths); + sass::string find_include(const sass::string& file, const sass::string& CWD, const StringVector paths, bool forImport, std::unordered_map& cache); // split a path string delimited by semicolons or colons (OS dependent) - sass::vector split_path_list(const char* paths); + // StringVector split_path_list(sass::string paths); // try to load the given filename // returned memory must be freed - // will auto convert .sass files - char* read_file(const sass::string& file); + char* slurp_file(const sass::string& file, const sass::string& CWD); - } - - // requested import - class Importer { - public: - // requested import path - sass::string imp_path; - // parent context path - sass::string ctx_path; - // base derived from context path - // this really just acts as a cache - sass::string base_path; - public: - Importer(sass::string imp_path, sass::string ctx_path) - : imp_path(File::make_canonical_path(imp_path)), - ctx_path(File::make_canonical_path(ctx_path)), - base_path(File::dir_name(ctx_path)) - { } - }; - - // a resolved include (final import) - class Include : public Importer { - public: - // resolved absolute path - sass::string abs_path; - public: - Include(const Importer& imp, sass::string abs_path) - : Importer(imp), abs_path(abs_path) - { } - }; - - // a loaded resource - class Resource { - public: - // the file contents - char* contents; - // connected sourcemap - char* srcmap; - public: - Resource(char* contents, char* srcmap) - : contents(contents), srcmap(srcmap) - { } - }; - - namespace File { + // Read and return resolved import + Import* read_import(const ResolvedImport& import); - sass::vector resolve_includes(const sass::string& root, const sass::string& file, - const sass::vector& exts = { ".scss", ".sass", ".css" }); + sass::vector resolve_includes(const sass::string& root, const sass::string& file, const sass::string& CWD, bool forImport, + std::unordered_map& cache, const std::vector& exts = { ".sass", ".scss", ".css" }); } diff --git a/src/flat_map.hpp b/src/flat_map.hpp new file mode 100644 index 0000000000..a50b0b8b27 --- /dev/null +++ b/src/flat_map.hpp @@ -0,0 +1,233 @@ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#ifndef SASS_FLAT_MAP_HPP +#define SASS_FLAT_MAP_HPP + +// sass.hpp must go before all system headers +// to get the __EXTENSIONS__ fix on Solaris. +#include "capi_sass.hpp" + +namespace Sass { + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + // A flat map is an optimized map when you know to expect minimal + // amount of items in the map. In this case we rather should use a + // vector, which is what this implementation does. It implements the + // interface of a regular map (partially) by utilizing a vector. + // My own tests indicate that hash maps start to win when 10 or more + // items are present. We can assume that e.g. functions typically don't + // have that many named arguments, probably most often not more than + // three or four. The interface matches std::unordered_map mostly. + // Having a compatible interface means we can exchange the + // implementations easily for performance benchmarks. + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + template class FlatMap + { + private: + + // Define base types + using PAIR = std::pair; + using TYPE = sass::vector; + + // Main key/value pair vector + TYPE items; + + public: + + // Some convenient iterator type aliases + using iterator = typename TYPE::iterator; + using const_iterator = typename TYPE::const_iterator; + using reverse_iterator = typename TYPE::reverse_iterator; + using const_reverse_iterator = typename TYPE::const_reverse_iterator; + + // Returns number of key/value pairs + size_t size() const + { + return items.size(); + } + // EO size + + // Returns if map is empty + bool empty() const + { + return items.empty(); + } + // EO empty + + // Erases all items + void clear() + { + items.clear(); + } + // EO clear + + // Returns the number of elements matching specific key + size_t count(const K& key) const + { + const_iterator cur = items.begin(); + const_iterator end = items.end(); + while (cur != end) { + // Compare the normalized keys + if (cur->first == key) { + return 1; + } + cur++; + } + return 0; + } + // EO count + + // Removes item with specific key from the map + void erase(const K& key) + { + const_iterator cur = items.begin(); + const_iterator end = items.end(); + while (cur != end) { + // Compare the normalized keys + if (cur->first == key) { + items.erase(cur); + return; + } + cur++; + } + } + // EO erase(key) + + // Removes iterator's item from the map + void erase(iterator it) + { + items.erase(it); + } + // EO erase(it) + + // Reserves space for at least the specified number of elements. + void reserve(size_t size) + { + items.reserve(size); + } + // EO reserve + + // Finds element with specific key + iterator find(const K& key) + { + iterator cur = items.begin(); + iterator end = items.end(); + while (cur != end) { + if (cur->first == key) { + return cur; + } + cur++; + } + return end; + } + // EO find + + // Finds element with specific key + const_iterator find(const K& key) const + { + const_iterator cur = items.begin(); + const_iterator end = items.end(); + while (cur != end) { + // Compare the normalized keys + if (cur->first == key) { + return cur; + } + cur++; + } + return end; + } + // EO const find + + // Access or insert specified element + V& operator[](const K& key) + { + iterator cur = items.begin(); + iterator end = items.end(); + while (cur != end) { + // Compare the normalized keys + if (cur->first == key) { + return cur->second; + } + cur++; + } + // Append empty object + items.push_back( + std::make_pair( + key, V{})); + // Returns newly added value + return items.back().second; + } + // EO array[] + + // Insert passed key/value pair + // ToDo: should return pair + bool insert(const PAIR& kv) + { + if (count(kv.first) == 0) { + // Append the pair + items.emplace_back(kv); + // Returns success + return true; + } + // Nothing inserted + return false; + } + // EO insert + + // Insert passed key/value pair + // ToDo: should return pair + bool insert(const PAIR&& kv) + { + if (count(kv.first) == 0) { + // Append the pair + items.emplace_back(std::move(kv)); + // Returns success + return true; + } + // Nothing inserted + return false; + } + // EO insert + + // Access element at specific key + // Throws of key is not known in map + const V& at(const K& key) const + { + const_iterator cur = items.begin(); + const_iterator end = items.end(); + while (cur != end) { + if (cur->first == key) { + return cur->second; + } + cur++; + } + // Throw an error if the item does not exist + throw std::runtime_error( + "Key does not exist"); + } + // EO at + + // Equality comparison operator + bool operator==(FlatMap rhs) const { + return items == rhs.items; + } + + // Returns an iterator for the begin or end position + const_iterator begin() const { return items.begin(); } + const_iterator end() const { return items.end(); } + // iterator begin() { return items.begin(); } + // iterator end() { return items.end(); } + + }; + // EO Class FlatMap + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + +}; + +#endif diff --git a/src/fn_colors.cpp b/src/fn_colors.cpp index aab277e3c1..8dd0706be2 100644 --- a/src/fn_colors.cpp +++ b/src/fn_colors.cpp @@ -1,596 +1,1848 @@ -// sass.hpp must go before all system headers to get the -// __EXTENSIONS__ fix on Solaris. -#include "sass.hpp" +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#include "fn_colors.hpp" #include -#include "ast.hpp" -#include "fn_utils.hpp" -#include "fn_colors.hpp" -#include "util.hpp" -#include "util_string.hpp" +#include "compiler.hpp" +#include "exceptions.hpp" +#include "ast_values.hpp" namespace Sass { namespace Functions { - bool string_argument(AST_Node_Obj obj) { - String_Constant* s = Cast(obj); - if (s == nullptr) return false; - const sass::string& str = s->value(); - return starts_with(str, "calc(") || - starts_with(str, "var("); - } + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// - void hsla_alpha_percent_deprecation(const SourceSpan& pstate, const sass::string val) - { + // Import string utility functions + using namespace StringUtils; - sass::string msg("Passing a percentage as the alpha value to hsla() will be interpreted"); - sass::string tail("differently in future versions of Sass. For now, use " + val + " instead."); + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // Create typedef for color function callback + typedef Value* (*colFn)( + const sass::string& name, + const ValueVector& arguments, + const SourceSpan& pstate, + Logger& logger, + bool strict); - deprecated(msg, tail, false, pstate); + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + double coerceToDeg(const Number* number) { + Units radiants("deg"); + // if (std::isinf(number->value())) return number->value(); + if (double factor = number->getUnitConversionFactor(radiants)) { + return number->value() * factor; + } + return number->value(); + // callStackFrame csf(compiler, number->pstate()); + // throw Exception::RuntimeException(compiler, "$" + vname + + // ": Expected " + number->inspect() + " to be an angle."); } - Signature rgb_sig = "rgb($red, $green, $blue)"; - BUILT_IN(rgb) + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // Returns whether [value] is an unquoted string + // that start with `var(` and contains `/`. + bool isVarSlash(Value* value) { - if ( - string_argument(env["$red"]) || - string_argument(env["$green"]) || - string_argument(env["$blue"]) - ) { - return SASS_MEMORY_NEW(String_Constant, pstate, "rgb(" - + env["$red"]->to_string() - + ", " - + env["$green"]->to_string() - + ", " - + env["$blue"]->to_string() - + ")" - ); - } - - return SASS_MEMORY_NEW(Color_RGBA, - pstate, - COLOR_NUM("$red"), - COLOR_NUM("$green"), - COLOR_NUM("$blue")); + if (value == nullptr) return false; + const String* str = value->isaString(); + if (str == nullptr) return false; + if (str->hasQuotes()) return false; + return startsWith(str->value(), "var(") && + str->value().find('/') != NPOS; } + // EO isVarSlash - Signature rgba_4_sig = "rgba($red, $green, $blue, $alpha)"; - BUILT_IN(rgba_4) + // Returns whether [value] is an unquoted + // string that start with `var(`. + bool isVar(const Value* value) { - if ( - string_argument(env["$red"]) || - string_argument(env["$green"]) || - string_argument(env["$blue"]) || - string_argument(env["$alpha"]) - ) { - return SASS_MEMORY_NEW(String_Constant, pstate, "rgba(" - + env["$red"]->to_string() - + ", " - + env["$green"]->to_string() - + ", " - + env["$blue"]->to_string() - + ", " - + env["$alpha"]->to_string() - + ")" - ); - } - - return SASS_MEMORY_NEW(Color_RGBA, - pstate, - COLOR_NUM("$red"), - COLOR_NUM("$green"), - COLOR_NUM("$blue"), - ALPHA_NUM("$alpha")); + if (value == nullptr) return false; + const String* str = value->isaString(); + if (str == nullptr) return false; + if (str->hasQuotes()) return false; + return startsWith(str->value(), "var("); } + // EO isVar - Signature rgba_2_sig = "rgba($color, $alpha)"; - BUILT_IN(rgba_2) + // Returns whether [value] is an unquoted + // string that start either with `calc(`, + // "var(", "env(", "min(" or "max(". + bool isSpecialNumber(const Value* value) { - if ( - string_argument(env["$color"]) - ) { - return SASS_MEMORY_NEW(String_Constant, pstate, "rgba(" - + env["$color"]->to_string() - + ", " - + env["$alpha"]->to_string() - + ")" - ); - } - - Color_RGBA_Obj c_arg = ARG("$color", Color)->toRGBA(); - - if ( - string_argument(env["$alpha"]) - ) { - sass::ostream strm; - strm << "rgba(" - << (int)c_arg->r() << ", " - << (int)c_arg->g() << ", " - << (int)c_arg->b() << ", " - << env["$alpha"]->to_string() - << ")"; - return SASS_MEMORY_NEW(String_Constant, pstate, strm.str()); - } - - Color_RGBA_Obj new_c = SASS_MEMORY_COPY(c_arg); - new_c->a(ALPHA_NUM("$alpha")); - new_c->disp(""); - return new_c.detach(); + if (value == nullptr) return false; + if (/*const Calculation* calc = */value->isaCalculation()) { + return true; + } + const String* str = value->isaString(); + if (str == nullptr) return false; + if (str->hasQuotes()) return false; + if (str->value().size() < 6) return false; + return startsWith(str->value(), "calc(", 5) + || startsWith(str->value(), "var(", 4) + || startsWith(str->value(), "env(", 4) + || startsWith(str->value(), "min(", 4) + || startsWith(str->value(), "max(", 4) + || startsWith(str->value(), "clamp(", 6); } + // EO isSpecialNumber - //////////////// - // RGB FUNCTIONS - //////////////// - - Signature red_sig = "red($color)"; - BUILT_IN(red) + // Implements regex check against /^[a-zA-Z]+\s*=/ + bool isMsFilterStart(const sass::string& text) { - Color_RGBA_Obj color = ARG("$color", Color)->toRGBA(); - return SASS_MEMORY_NEW(Number, pstate, color->r()); + auto it = text.begin(); + // The filter must start with alpha + if (!Character::isAlphabetic(*it)) return false; + while (it != text.end() && Character::isAlphabetic(*it)) ++it; + while (it != text.end() && Character::isWhitespace(*it)) ++it; + return it != text.end() && *it == '='; } + // EO isMsFilterStart - Signature green_sig = "green($color)"; - BUILT_IN(green) +/// Prints a deprecation warning if [hue] has a unit other than `deg`. + void checkAngle(Logger& logger, const Number* angle, const sass::string& name) { - Color_RGBA_Obj color = ARG("$color", Color)->toRGBA(); - return SASS_MEMORY_NEW(Number, pstate, color->g()); + + if (!angle->hasUnits() || angle->hasUnit("deg")) return; + + sass::sstream message; + message << "$" << name << ": Passing a unit other than deg ("; + message << angle->inspect() << ") is deprecated." << STRMLF; + + if (angle->numerators.size() == 1 && angle->denominators.size() == 0 && + get_unit_class(string_to_unit(angle->numerators[0])) == UnitClass::ANGLE) + { + double coerced = angle->getUnitConversionFactor(Strings::deg) * angle->value(); + Number correct(angle->pstate(), coerced, "deg"); + Number wrong(angle->pstate(), angle->value(), "deg"); + message << "You're passing " << angle->inspect() << ", which is currently (incorrectly) converted to " << wrong.inspect() << "." << STRMLF; + message << "Soon, it will instead be correctly converted to " << correct.inspect() << "." << STRMLF << STRMLF; + message << "To preserve current behavior: $" << name << " * 1deg/1" << angle->numerators[0] << STRMLF; + message << "To migrate to new behavior: 0deg + $" << name << STRMLF; + } + else { + StringVector dif(angle->numerators); + StringVector mul(angle->denominators); + // ToDo: don't report percentage twice!? + for (auto& unit : mul) unit = " * 1" + unit; + for (auto& unit : dif) unit = " / 1" + unit; + message << STRMLF << "To preserve current behavior: $" << name + << StringUtils::join(mul, "") << StringUtils::join(dif, "") << STRMLF; + } + + message << STRMLF << "See https://sass-lang.com/d/color-units" << STRMLF; + logger.addDeprecation(message.str(), angle->pstate(), Logger::WARN_ANGLE_CONVERT); } - Signature blue_sig = "blue($color)"; - BUILT_IN(blue) + // Helper function for debugging + // ToDo return EnvKey? + const sass::string& getColorArgName( + size_t idx, const sass::string& name) { - Color_RGBA_Obj color = ARG("$color", Color)->toRGBA(); - return SASS_MEMORY_NEW(Number, pstate, color->b()); + switch (idx) { + case 0: return name[0] == Character::$h ? Strings::hue : Strings::red; + case 1: return name[0] == Character::$h ? name[1] == Character::$s ? Strings::saturation : Strings::whiteness : Strings::green; + case 2: return name[0] == Character::$h ? name[1] == Character::$s ? Strings::lightness : Strings::blackness : Strings::blue; + default: throw std::runtime_error("Invalid input argument"); + } } - - Color_RGBA* colormix(Context& ctx, SourceSpan& pstate, Color* color1, Color* color2, double weight) { - Color_RGBA_Obj c1 = color1->toRGBA(); - Color_RGBA_Obj c2 = color2->toRGBA(); - double p = weight/100; - double w = 2*p - 1; - double a = c1->a() - c2->a(); - - double w1 = (((w * a == -1) ? w : (w + a)/(1 + w*a)) + 1)/2.0; - double w2 = 1 - w1; - - return SASS_MEMORY_NEW(Color_RGBA, - pstate, - Sass::round(w1*c1->r() + w2*c2->r(), ctx.c_options.precision), - Sass::round(w1*c1->g() + w2*c2->g(), ctx.c_options.precision), - Sass::round(w1*c1->b() + w2*c2->b(), ctx.c_options.precision), - c1->a()*p + c2->a()*(1-p)); + // EO getColorArgName + + // Return value that will render as-is in css + String* getFunctionString( + const sass::string& name, + const SourceSpan& pstate, + const ValueVector& arguments = {}, + SassSeparator separator = SASS_COMMA) + { + bool addComma = false; + sass::sstream fncall; + fncall << name << "("; + sass::string sep(" "); + if (separator == SASS_COMMA) sep = ", "; + for (Value* arg : arguments) { + if (addComma) fncall << sep; + fncall << arg->toCss(); + addComma = true; + } + fncall << ")"; + return SASS_MEMORY_NEW(String, + pstate, fncall.str()); } + // EO getFunctionString - Signature mix_sig = "mix($color1, $color2, $weight: 50%)"; - BUILT_IN(mix) + Value* parseColorChannels( + const sass::string& name, + Value* channels, + const SourceSpan& pstate, + Compiler& compiler) { - 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); - - } + // Check for css var + if (isVar(channels)) { + return SASS_MEMORY_NEW( + String, pstate, name + "(" + + channels->inspect() + ")"); + } - //////////////// - // HSL FUNCTIONS - //////////////// + auto originalChannels = channels; + ValueObj alphaFromSlashList; + if (channels->separator() == SASS_DIV) { + + // std::cerr << "List from slash\n"; + + ListObj args = SASS_MEMORY_NEW(List, channels->pstate(), + { channels->start(), channels->stop() }); + if (args->size() != 2) { + sass::sstream message; + message << "Only 2 slash-separated elements allowed, but "; + message << channels->lengthAsList() << " "; + message << pluralize("was", channels->lengthAsList(), "were"); + message << " passed."; + throw Exception::SassScriptException(message.str(), compiler, pstate); + } - 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); + alphaFromSlashList = args->get(1); + if (!isSpecialNumber(alphaFromSlashList)) { + alphaFromSlashList->assertNumber(compiler, "alpha"); + } + if (isVar(args->get(0))) { + // std::cerr << "Doing shenanigans\n"; + return getFunctionString(name, pstate, { originalChannels }); + // return _functionString(name, [originalChannels]); + } - } + channels = args->get(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); - sass::string nr(val->to_string(ctx.c_options)); - hsla_alpha_percent_deprecation(pstate, nr); - } - - return SASS_MEMORY_NEW(Color_HSLA, - pstate, - ARGVAL("$hue"), - ARGVAL("$saturation"), - ARGVAL("$lightness"), - ARGVAL("$alpha")); + // list = args; + } - } + // Check if argument is already a list + ListObj list = channels->isaList(); + // If not create one and wrap value in it + if (!list) { + list = SASS_MEMORY_NEW(List, + pstate, { channels->start(), channels->stop() }); + } - ///////////////////////////////////////////////////////////////////////// - // Query functions - ///////////////////////////////////////////////////////////////////////// + // Check for invalid input arguments + bool isBracketed = list->hasBrackets(); + bool isCommaSeparated = list->hasCommaSeparator(); + if (isCommaSeparated || isBracketed) { + sass::sstream msg; + msg << "$channels must be"; + if (isBracketed) msg << " an unbracketed"; + if (isCommaSeparated) { + msg << (isBracketed ? "," : " a"); + msg << " space-separated"; + } + msg << " list."; + callStackFrame csf(compiler, list->pstate()); + throw Exception::RuntimeException(compiler, msg.str()); + } - 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"); - } + // Check if we have a string as first argument + if (list->size() > 0) { + if (auto prefix = list->get(0)->isaString()) { + if (prefix->hasQuotes() == false) { + if (StringUtils::equalsIgnoreCase(prefix->value(), "from", 4)) { + return new String(pstate, name + "(" + originalChannels->inspect() + ")"); + } + } + } + } - Signature saturation_sig = "saturation($color)"; - BUILT_IN(saturation) - { - Color_HSLA_Obj col = ARG("$color", Color)->toHSLA(); - return SASS_MEMORY_NEW(Number, pstate, col->s(), "%"); - } + // Check if we have too many arguments + if (list->size() > 3) { + callStackFrame csf(compiler, list->pstate()); + throw Exception::TooManyArguments(compiler, list->size(), 3); + } + // Check for not enough arguments + if (list->size() < 3) { + // Check if we have any css vars + bool hasVar = false; + for (Value* item : list->elements()) { + if (isVar(item)) { + hasVar = true; + break; + } + } + // Return function as-is back to be rendered as css + if (hasVar || (!list->empty() && isVarSlash(list->last()))) { + return getFunctionString(name, pstate, { originalChannels }); + } + // Throw error for missing argument + throw Exception::MissingArgument(compiler, + getColorArgName(list->size(), name)); + } - Signature lightness_sig = "lightness($color)"; - BUILT_IN(lightness) - { - Color_HSLA_Obj col = ARG("$color", Color)->toHSLA(); - return SASS_MEMORY_NEW(Number, pstate, col->l(), "%"); - } + if (alphaFromSlashList) { + ListObj copy = SASS_MEMORY_COPY(list); + list->append(alphaFromSlashList); + return list.detach(); + } - ///////////////////////////////////////////////////////////////////////// - // HSL manipulation functions - ///////////////////////////////////////////////////////////////////////// - Signature adjust_hue_sig = "adjust-hue($color, $degrees)"; - BUILT_IN(adjust_hue) + // Check for the second argument + Number* secondNumber = list->get(2)->isaNumber(); + String* secondString = list->get(2)->isaString(); + if (secondNumber && secondNumber->hasAsSlash()) { + return SASS_MEMORY_NEW(List, pstate, { + list->get(0), list->get(1), + secondNumber->lhsAsSlash().ptr(), + secondNumber->rhsAsSlash().ptr() + }); + } + if (secondString && !secondString->hasQuotes() + && secondString->value().find('/') != NPOS) { + return getFunctionString(name, pstate, + list->elements(), list->separator()); + } + // Return arguments + return list.detach(); + } + // EO parseColorChannels + + // Handle one argument function invocation + // Used by color functions rgb, hsl and hwb + Value* handleOneArgColorFn( + const sass::string& name, + Value* argument, + colFn function, + Compiler& compiler, + SourceSpan pstate, + bool strict) { - 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(); + // Parse the color channel arguments + ValueObj parsed = parseColorChannels( + name, argument, pstate, compiler); + // Return if it is a string + if (parsed->isaString()) { + return parsed.detach(); + } + // Execute function with list of arguments + if (const List* list = parsed->isaList()) { + return (*function)(name, list->elements(), pstate, compiler, strict); + } + // Otherwise return + return argument; + // Not sure if we must stringify + // return SASS_MEMORY_NEW(String, + // pstate, argument->inspect()); } - - Signature lighten_sig = "lighten($color, $amount)"; - BUILT_IN(lighten) + // EO handleOneArgColorFn + + /// Returns [color1] and [color2], mixed + // together and weighted by [weight]. + ColorRgba* mixColors( + const Color* color1, + const Color* color2, + const Number* weight, + const SourceSpan& pstate, + Logger& logger) { - 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(); - + ColorRgbaObj lhs(color1->toRGBA()); + ColorRgbaObj rhs(color2->toRGBA()); + // This algorithm factors in both the user-provided weight (w) and the + // difference between the alpha values of the two colors (a) to decide how + // to perform the weighted average of the two RGB values. + // It works by first normalizing both parameters to be within [-1, 1], where + // 1 indicates "only use color1", -1 indicates "only use color2", and all + // values in between indicated a proportionately weighted average. + // Once we have the normalized variables w and a, we apply the formula + // (w + a)/(1 + w*a) to get the combined weight (in [-1, 1]) of color1. This + // formula has two especially nice properties: + // * When either w or a are -1 or 1, the combined weight is also that + // number (cases where w * a == -1 are undefined, and handled as a + // special case). + // * When a is 0, the combined weight is w, and vice versa. + // Finally, the weight of color1 is renormalized to be within [0, 1] and the + // weight of color2 is given by 1 minus the weight of color1. + double weightScale = weight->assertRange( + 0.0, 100.0, unit_percent, logger, "weight") / 100.0; + double normalizedWeight = weightScale * 2.0 - 1.0; + double alphaDistance = lhs->a() - color2->a(); + double combinedWeight1 = normalizedWeight * alphaDistance == -1 + ? normalizedWeight : (normalizedWeight + alphaDistance) / + (1.0 + normalizedWeight * alphaDistance); + double weight1 = (combinedWeight1 + 1.0) / 2.0; + double weight2 = 1.0 - weight1; + return SASS_MEMORY_NEW(ColorRgba, pstate, + fuzzyRound(lhs->r() * weight1 + rhs->r() * weight2, logger.epsilon), + fuzzyRound(lhs->g() * weight1 + rhs->g() * weight2, logger.epsilon), + fuzzyRound(lhs->b() * weight1 + rhs->b() * weight2, logger.epsilon), + lhs->a() * weightScale + rhs->a() * (1 - weightScale)); } + // EO mixColor - Signature darken_sig = "darken($color, $amount)"; - BUILT_IN(darken) + double scaleValue( + double current, + double scale, + double max) { - 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(); + return current + (scale > 0.0 ? max - current : current) * scale; } - Signature saturate_sig = "saturate($color, $amount: false)"; - BUILT_IN(saturate) + // Asserts that [number] is a percentage or has no units, and normalizes the + // value. If [number] has no units, its value is clamped to be greater than `0` + // or less than [max] and returned. If [number] is a percentage, it's scaled to + // be within `0` and [max]. Otherwise, this throws a [SassScriptException]. + // [name] is used to identify the argument in the error message. + double _percentageOrUnitless( + const Number* number, double max, + const sass::string& name, + Logger& traces) { - // 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 value = 0.0; + if (!number->hasUnits()) { + value = number->value(); } - - 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(); + else if (number->hasUnit(Strings::percent)) { + value = max * number->value() / 100; + } + else { + callStackFrame csf(traces, number->pstate()); + throw Exception::RuntimeException(traces, + name + ": Expected " + number->inspect() + + " to have no units or \"%\"."); + } + if (value < 0.0) return 0.0; + if (value > max) return max; + return value; } - Signature desaturate_sig = "desaturate($color, $amount)"; - BUILT_IN(desaturate) + String* _functionRgbString(sass::string name, ColorRgba* color, Value* alpha, const SourceSpan& pstate) { - 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(); + sass::sstream fncall; + fncall << name << "("; + fncall << color->r() << ", "; + fncall << color->g() << ", "; + fncall << color->b() << ", "; + fncall << alpha->inspect() << ")"; + return SASS_MEMORY_NEW(String, + pstate, fncall.str()); } - Signature grayscale_sig = "grayscale($color)"; - BUILT_IN(grayscale) + Value* handleTwoArgRgb(sass::string name, ValueVector arguments, const SourceSpan& pstate, Logger& logger, bool strict) { - // 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) + ")"); + // Check if any `calc()` or `var()` are passed + if (isVar(arguments[0])) { + return getFunctionString( + name, pstate, arguments); + } + else if (isVar(arguments[1])) { + if (const Color* first = arguments[0]->isaColor()) { + ColorRgbaObj rgba = first->toRGBA(); + return _functionRgbString(name, + rgba, arguments[1], pstate); + } + else { + return getFunctionString( + name, pstate, arguments); + } + } + else if (!strict && isSpecialNumber(arguments[1])) { + if (const Color* color = arguments[0]->assertColor(logger, Strings::color)) { + ColorRgbaObj rgba = color->toRGBA(); + return _functionRgbString(name, + rgba, arguments[1], pstate); + } } - Color* col = ARG("$color", Color); - Color_HSLA_Obj copy = col->copyAsHSLA(); - copy->s(0.0); // just reset saturation + const Color* color = arguments[0]->assertColor(logger, Strings::color); + const Number* alpha = arguments[1]->assertNumber(logger, Strings::alpha); + ColorObj copy = SASS_MEMORY_COPY(color); + copy->a(_percentageOrUnitless( + alpha, 1.0, "$alpha", logger)); + copy->parsed(false); 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(); - } + namespace Colors { - Signature invert_sig = "invert($color, $weight: 100%)"; - BUILT_IN(invert) - { - // CSS3 filter function overload: pass literal through directly - Number* amount = Cast(env["$color"]); - double weight = DARG_U_PRCT("$weight"); - if (amount) { - // TODO: does not throw on 100% manually passed as value - if (weight < 100.0) { - error("Only one argument may be passed to the plain-CSS invert() function.", pstate, traces); - } - return SASS_MEMORY_NEW(String_Quoted, pstate, "invert(" + amount->to_string(ctx.c_options) + ")"); - } - - Color* col = ARG("$color", Color); - Color_RGBA_Obj inv = col->copyAsRGBA(); - inv->r(clip(255.0 - inv->r(), 0.0, 255.0)); - inv->g(clip(255.0 - inv->g(), 0.0, 255.0)); - inv->b(clip(255.0 - inv->b(), 0.0, 255.0)); - return colormix(ctx, pstate, inv, col, weight); - } + /*******************************************************************/ - ///////////////////////////////////////////////////////////////////////// - // Opacity functions - ///////////////////////////////////////////////////////////////////////// + BUILT_IN_FN(rgb4arg) + { + return rgbFn(Strings::rgb, + arguments, pstate, compiler, false); + } - Signature alpha_sig = "alpha($color)"; - Signature opacity_sig = "opacity($color)"; - BUILT_IN(alpha) - { - String_Constant* ie_kwd = Cast(env["$color"]); - if (ie_kwd) { - return SASS_MEMORY_NEW(String_Quoted, pstate, "alpha(" + ie_kwd->value() + ")"); + BUILT_IN_FN(rgb3arg) + { + return rgbFn(Strings::rgb, + arguments, pstate, compiler, false); } - // CSS3 filter function overload: pass literal through directly - Number* amount = Cast(env["$color"]); - if (amount) { - return SASS_MEMORY_NEW(String_Quoted, pstate, "opacity(" + amount->to_string(ctx.c_options) + ")"); + BUILT_IN_FN(fnRgb4arg) + { + return rgbFn(Strings::rgb, + arguments, pstate, compiler, true); } - return SASS_MEMORY_NEW(Number, pstate, ARG("$color", Color)->a()); - } + BUILT_IN_FN(fnRgb3arg) + { + return rgbFn(Strings::rgb, + arguments, pstate, compiler, true); + } - 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(); - } + BUILT_IN_FN(rgb2arg) + { + return handleTwoArgRgb(Strings::rgb, + arguments, pstate, compiler, false); + } - Signature transparentize_sig = "transparentize($color, $amount)"; - Signature fade_out_sig = "fade-out($color, $amount)"; - BUILT_IN(transparentize) - { - Color* col = ARG("$color", Color); - double amount = DARG_U_FACT("$amount"); - Color_Obj copy = SASS_MEMORY_COPY(col); - copy->a(std::max(col->a() - amount, 0.0)); - return copy.detach(); - } + BUILT_IN_FN(fnRgb2arg) + { + return handleTwoArgRgb(Strings::rgb, + arguments, pstate, compiler, true); + } - //////////////////////// - // OTHER COLOR FUNCTIONS - //////////////////////// + BUILT_IN_FN(rgb1arg) + { + #if SassPreserveColorInfo + if (Color* color = arguments[0]->isaColor()) { + Color* rgb = color->toRGBA(); + rgb->a(1.0); + return rgb; + } + #endif + return handleOneArgColorFn(Strings::rgb, + arguments[0], &rgbFn, compiler, pstate, false); + } + + BUILT_IN_FN(fnRgb1arg) + { + std::cerr << "Hello dear\n"; + #if SassPreserveColorInfo + if (Color* color = arguments[0]->isaColor()) { + Color* rgb = color->toRGBA(); + rgb->a(1.0); + return rgb; + } + #endif + return handleOneArgColorFn(Strings::rgb, + arguments[0], &rgbFn, compiler, pstate, true); + } + + /*******************************************************************/ + + BUILT_IN_FN(rgba4arg) + { + return rgbFn(Strings::rgba, + arguments, pstate, compiler, false); + } + + BUILT_IN_FN(rgba3arg) + { + return rgbFn(Strings::rgba, + arguments, pstate, compiler, false); + } + + BUILT_IN_FN(fnRgba4arg) + { + return rgbFn(Strings::rgba, + arguments, pstate, compiler, true); + } + + BUILT_IN_FN(fnRgba3arg) + { + return rgbFn(Strings::rgba, + arguments, pstate, compiler, true); + } + + BUILT_IN_FN(rgba2arg) + { + return handleTwoArgRgb(Strings::rgba, + arguments, pstate, compiler, false); + } + + BUILT_IN_FN(fnRgba2arg) + { + return handleTwoArgRgb(Strings::rgba, + arguments, pstate, compiler, true); + } + + BUILT_IN_FN(rgba1arg) + { + #if SassPreserveColorInfo + if (Color* color = arguments[0]->isaColor()) { + return color->toRGBA(); + } + #endif + return handleOneArgColorFn(Strings::rgba, + arguments[0], &rgbFn, compiler, pstate, false); + } + + BUILT_IN_FN(fnRgba1arg) + { + #if SassPreserveColorInfo + if (Color* color = arguments[0]->isaColor()) { + return color->toRGBA(); + } + #endif + return handleOneArgColorFn(Strings::rgba, + arguments[0], &rgbFn, compiler, pstate, true); + } + + /*******************************************************************/ + + BUILT_IN_FN(hsl4arg) + { + return hslFn(Strings::hsl, + arguments, pstate, compiler, false); + } + + BUILT_IN_FN(hsl3arg) + { + return hslFn(Strings::hsl, + arguments, pstate, compiler, false); + } + + BUILT_IN_FN(fnHsl4arg) + { + return hslFn(Strings::hsl, + arguments, pstate, compiler, true); + } + + BUILT_IN_FN(fnHsl3arg) + { + return hslFn(Strings::hsl, + arguments, pstate, compiler, true); + } + + BUILT_IN_FN(hsl2arg) + { + // hsl(123, var(--foo)) is valid CSS because --foo might be `10%, 20%` + // and functions are parsed after variable substitution. + if (isVar(arguments[0]) || isVar(arguments[1])) { + return getFunctionString(Strings::hsl, pstate, arguments); + } + // Otherwise throw error for missing argument + throw Exception::MissingArgument(compiler, key_lightness); + } + + BUILT_IN_FN(fnHsl2arg) + { + // Otherwise throw error for missing argument + throw Exception::TooManyArguments(compiler, 2, 1); + } + + BUILT_IN_FN(hsl1arg) + { + #if SassPreserveColorInfo + if (Color* color = arguments[0]->isaColor()) { + Color* hsl = color->toHSLA(); + hsl->a(1.0); + return hsl; + } + #endif + return handleOneArgColorFn(Strings::hsl, + arguments[0], &hslFn, compiler, pstate, false); + } + + BUILT_IN_FN(fnHsl1arg) + { + #if SassPreserveColorInfo + if (Color* color = arguments[0]->isaColor()) { + Color* hsl = color->toHSLA(); + hsl->a(1.0); + return hsl; + } + #endif + return handleOneArgColorFn(Strings::hsl, + arguments[0], &hslFn, compiler, pstate, true); + } + + /*******************************************************************/ + + BUILT_IN_FN(hsla4arg) + { + return hslFn(Strings::hsla, arguments, pstate, compiler, false); + } + + BUILT_IN_FN(hsla3arg) + { + return hslFn(Strings::hsla, arguments, pstate, compiler, false); + } + + BUILT_IN_FN(fnHsla4arg) + { + return hslFn(Strings::hsla, arguments, pstate, compiler, true); + } + + BUILT_IN_FN(fnHsla3arg) + { + return hslFn(Strings::hsla, arguments, pstate, compiler, true); + } + + BUILT_IN_FN(hsla2arg) + { + // hsl(123, var(--foo)) is valid CSS because --foo might be `10%, 20%` + // and functions are parsed after variable substitution. + if (isVar(arguments[0]) || isVar(arguments[1])) { + return getFunctionString(Strings::hsla, pstate, arguments); + } + // Otherwise throw error for missing argument + throw Exception::MissingArgument(compiler, key_lightness); + } + + BUILT_IN_FN(fnHsla2arg) + { + // Otherwise throw error for missing argument + throw Exception::TooManyArguments(compiler, 2, 1); + } + + BUILT_IN_FN(hsla1arg) + { + #if SassPreserveColorInfo + if (Color* color = arguments[0]->isaColor()) { + return color->toHSLA(); + } + #endif + return handleOneArgColorFn(Strings::hsla, + arguments[0], &hslFn, compiler, pstate, false); + } + + BUILT_IN_FN(fnHsla1arg) + { + #if SassPreserveColorInfo + if (Color* color = arguments[0]->isaColor()) { + return color->toHSLA(); + } + #endif + return handleOneArgColorFn(Strings::hsla, + arguments[0], &hslFn, compiler, pstate, true); + } + + /*******************************************************************/ + + BUILT_IN_FN(hwb4arg) + { + return hwbFn(Strings::hwb, + arguments, pstate, compiler, false); + } + + + BUILT_IN_FN(hwb3arg) + { + return hwbFn(Strings::hwb, + arguments, pstate, compiler, false); + } + + BUILT_IN_FN(fnHwb4arg) + { + return hwbFn(Strings::hwb, + arguments, pstate, compiler, true); + } + + + BUILT_IN_FN(fnHwb3arg) + { + return hwbFn(Strings::hwb, + arguments, pstate, compiler, true); + } + + BUILT_IN_FN(hwb2arg) + { + return getFunctionString(Strings::hwb, pstate, arguments); + } + + BUILT_IN_FN(fnHwb2arg) + { + // Otherwise throw error for missing argument + throw Exception::TooManyArguments(compiler, 2, 1); + } + + BUILT_IN_FN(hwb1arg) + { + #if SassPreserveColorInfo + if (Color* color = arguments[0]->isaColor()) { + Color* hwb = color->toHWBA(); + hwb->a(1.0); + return hwb; + } + #endif + return handleOneArgColorFn(Strings::hwb, + arguments[0], &hwbFn, compiler, pstate, false); + } + + BUILT_IN_FN(fnHwb1arg) + { + #if SassPreserveColorInfo + if (Color* color = arguments[0]->isaColor()) { + Color* hwb = color->toHWBA(); + hwb->a(1.0); + return hwb; + } + #endif + ValueObj value = handleOneArgColorFn(Strings::hwb, + arguments[0], &hwbFn, compiler, pstate, true); + if (value->isaString()) { + throw Exception::RuntimeException(compiler, "Expected " + "numeric channels, got \"" + value->inspect() + "\"."); + } + return value.detach(); + } + + /*******************************************************************/ + + BUILT_IN_FN(hwba4arg) + { + return hwbFn(Strings::hwba, arguments, pstate, compiler, false); + } + + BUILT_IN_FN(hwba3arg) + { + return hwbFn(Strings::hwba, arguments, pstate, compiler, false); + } + + BUILT_IN_FN(fnHwba4arg) + { + return hwbFn(Strings::hwba, arguments, pstate, compiler, true); + } + + BUILT_IN_FN(fnHwba3arg) + { + return hwbFn(Strings::hwba, arguments, pstate, compiler, true); + } + + BUILT_IN_FN(hwba2arg) + { + return getFunctionString(Strings::hwba, pstate, arguments); + } + + BUILT_IN_FN(fnHwba2arg) + { + throw Exception::TooManyArguments(compiler, 2, 1); + } + + BUILT_IN_FN(hwba1arg) + { + #if SassPreserveColorInfo + if (Color* color = arguments[0]->isaColor()) { + return color->toHWBA(); + } + #endif + return handleOneArgColorFn(Strings::hwba, + arguments[0], &hwbFn, compiler, pstate, false); + } + + BUILT_IN_FN(fnHwba1arg) + { + #if SassPreserveColorInfo + if (Color* color = arguments[0]->isaColor()) { + return color->toHWBA(); + } + #endif + ValueObj value = handleOneArgColorFn(Strings::hwba, + arguments[0], &hwbFn, compiler, pstate, true); + if (value->isaString()) { + throw Exception::RuntimeException(compiler, "Expected " + "numeric channels, got \"" + value->inspect() + "\"."); + } + return value.detach(); + } + + /*******************************************************************/ + + BUILT_IN_FN(red) + { + const Color* color = arguments[0]->assertColor(compiler, Strings::color); + ColorRgbaObj rgba(color->toRGBA()); // This might create a copy + return SASS_MEMORY_NEW(Number, pstate, Sass::round64(rgba->r(), compiler.epsilon)); + } + + BUILT_IN_FN(green) + { + const Color* color = arguments[0]->assertColor(compiler, Strings::color); + ColorRgbaObj rgba(color->toRGBA()); // This might create a copy + return SASS_MEMORY_NEW(Number, pstate, Sass::round64(rgba->g(), compiler.epsilon)); + } + + BUILT_IN_FN(blue) + { + const Color* color = arguments[0]->assertColor(compiler, Strings::color); + ColorRgbaObj rgba(color->toRGBA()); // This might create a copy + return SASS_MEMORY_NEW(Number, pstate, Sass::round64(rgba->b(), compiler.epsilon)); + } + + /*******************************************************************/ + + BUILT_IN_FN(invert) + { + const Number* weight = arguments[1]->assertNumber(compiler, Strings::weight); + + //if (isSpecialNumber(arguments[0])) { + // return getFunctionString( + // Strings::invert, + // pstate, arguments); + //} + if (arguments[0]->isaNumber() || isSpecialNumber(arguments[0]) /* or isSpecialValue*/) { + // Allow only the value `100` or a percentage (unit == `% `) + const Number* weight = arguments[1]->assertNumber(compiler, Strings::weight); + if (weight->value() != 100 || !weight->hasUnit(Strings::percent)) { + throw Exception::RuntimeException(compiler, + "Only one argument may be passed " + "to the plain-CSS invert() function."); + } + // Return function string since first argument was a number + // Need to remove the weight argument as it has a default value + return getFunctionString(Strings::invert, pstate, { arguments[0] }); + } + const Color* color = arguments[0]->assertColor(compiler, Strings::color); + ColorRgbaObj inverse(color->copyAsRGBA()); // Make a copy! + inverse->r(clamp(255.0 - inverse->r(), 0.0, 255.0)); + inverse->g(clamp(255.0 - inverse->g(), 0.0, 255.0)); + inverse->b(clamp(255.0 - inverse->b(), 0.0, 255.0)); + // Note: mixColors will create another unnecessary copy! + return mixColors(inverse, color, weight, pstate, compiler); + } + + BUILT_IN_FN(fnInvert) + { + if (isSpecialNumber(arguments[0])) { + return getFunctionString( + Strings::invert, + pstate, arguments); + } + + // if (arguments[0]->isaNumber()) { + // compiler.addWarning("Passing a number to " + // "color.invert() is deprecated.\n\nRecommendation: " + // "invert(" + arguments[0]->inspect() + ")", + // arguments[0]->pstate(), + // Logger::WARN_NUMBER_ARG); + // } + return invert(pstate, arguments, compiler, eval); + } + + /*******************************************************************/ + + BUILT_IN_FN(hue) + { + const Color* color = arguments[0]->assertColor(compiler, Strings::color); + ColorHslaObj hsla(color->toHSLA()); // This probably creates a copy + return SASS_MEMORY_NEW(Number, pstate, hsla->h(), Strings::deg); + } + + BUILT_IN_FN(saturation) + { + const Color* color = arguments[0]->assertColor(compiler, Strings::color); + ColorHslaObj hsla(color->toHSLA()); // This probably creates a copy + return SASS_MEMORY_NEW(Number, pstate, hsla->s(), Strings::percent); + } + + BUILT_IN_FN(lightness) + { + const Color* color = arguments[0]->assertColor(compiler, Strings::color); + ColorHslaObj hsla(color->toHSLA()); // This probably creates a copy + return SASS_MEMORY_NEW(Number, pstate, hsla->l(), Strings::percent); + } + + BUILT_IN_FN(noLighten) + { + throw Exception::DeprecatedColorAdjustFn(compiler, + arguments, "lighten", "$lightness: "); + } + + BUILT_IN_FN(noDarken) + { + throw Exception::DeprecatedColorAdjustFn(compiler, + arguments, "darken", "$lightness: -"); + } + + BUILT_IN_FN(whiteness) + { + const Color* color = arguments[0]->assertColor(compiler, Strings::color); + #if SassPreserveColorInfo + ColorHwbaObj hwba(color->toHWBA()); + #else + ColorRgbaObj rgba(color->copyAsRGBA()); + rgba->r(round64(rgba->r(), compiler.epsilon)); + rgba->g(round64(rgba->g(), compiler.epsilon)); + rgba->b(round64(rgba->b(), compiler.epsilon)); + ColorHwbaObj hwba(rgba->toHWBA()); + #endif + return SASS_MEMORY_NEW(Number, pstate, hwba->w(), Strings::percent); + } + + BUILT_IN_FN(blackness) + { + const Color* color = arguments[0]->assertColor(compiler, Strings::color); + #if SassPreserveColorInfo + ColorHwbaObj hwba(color->toHWBA()); + #else + ColorRgbaObj rgba(color->copyAsRGBA()); + rgba->r(round64(rgba->r(), compiler.epsilon)); + rgba->g(round64(rgba->g(), compiler.epsilon)); + rgba->b(round64(rgba->b(), compiler.epsilon)); + ColorHwbaObj hwba(rgba->toHWBA()); + #endif + return SASS_MEMORY_NEW(Number, pstate, hwba->b(), Strings::percent); + } + + /*******************************************************************/ + + BUILT_IN_FN(adjustHue) + { + const Color* color = arguments[0]->assertColor(compiler, Strings::color); + const Number* degrees = arguments[1]->assertNumber(compiler, Strings::degrees); + checkAngle(compiler, degrees, Strings::degrees); + ColorHslaObj copy(color->copyAsHSLA()); // Must make a copy! + copy->h(absmod(copy->h() + coerceToDeg(degrees), 360.0)); + return copy.detach(); + } + + BUILT_IN_FN(noAdjustHue) + { + throw Exception::DeprecatedColorAdjustFn(compiler, + arguments, "adjust-hue", "$hue: ", Strings::degrees); + } + + BUILT_IN_FN(complement) + { + const Color* color = arguments[0]->assertColor(compiler, Strings::color); + ColorHslaObj copy(color->copyAsHSLA()); // Must make a copy! + copy->h(absmod(copy->h() + 180.0, 360.0)); + return copy.detach(); + } + + /*******************************************************************/ + + BUILT_IN_FN(grayscale) + { + // Gracefully handle if number is passed + if (arguments[0]->isaNumber() || isSpecialNumber(arguments[0])) { + return getFunctionString( + Strings::grayscale, + pstate, arguments); + } + const Color* color = arguments[0]->assertColor(compiler, Strings::color); + ColorHslaObj copy(color->copyAsHSLA()); // Must make a copy! + copy->s(0.0); // Simply reset the saturation + return copy.detach(); // Return HSLA + } + + BUILT_IN_FN(lighten) + { + const Color* color = arguments[0]->assertColor(compiler, Strings::color); + const Number* amount = arguments[1]->assertNumber(compiler, Strings::amount); + double nr = amount->assertRange(0.0, 100.0, amount, compiler, Strings::amount); + ColorHslaObj copy(color->copyAsHSLA()); // Must make a copy! + copy->l(clamp(copy->l() + nr, 0.0, 100.0)); + return copy.detach(); // Return HSLA + } + + BUILT_IN_FN(darken) + { + const Color* color = arguments[0]->assertColor(compiler, Strings::color); + const Number* amount = arguments[1]->assertNumber(compiler, Strings::amount); + double nr = amount->assertRange(0.0, 100.0, amount, compiler, Strings::amount); + ColorHslaObj copy(color->copyAsHSLA()); // Must make a copy! + copy->l(clamp(copy->l() - nr, 0.0, 100.0)); + return copy.detach(); // Return HSLA + } + + /*******************************************************************/ + + BUILT_IN_FN(saturate2arg) + { + const Color* color = arguments[0]->assertColor(compiler, Strings::color); + const Number* amount = arguments[1]->assertNumber(compiler, Strings::amount); + double nr = amount->assertRange(0.0, 100.0, amount, compiler, Strings::amount); + ColorHslaObj copy(color->copyAsHSLA()); // Must make a copy! + if (copy->h() == 0 && nr > 0.0) copy->h(100.0); + copy->s(clamp(copy->s() + nr, 0.0, 100.0)); + return copy.detach(); // Return HSLA + } + + BUILT_IN_FN(saturate1arg) + { + if (arguments[0]->isaNumber() || isSpecialNumber(arguments[0])) { + return getFunctionString( + Strings::saturate, + pstate, arguments); + } + arguments[0]->assertNumber(compiler, Strings::amount); + return getFunctionString(Strings::saturate, pstate, { arguments[0] }); + } + + BUILT_IN_FN(desaturate) + { + const Color* color = arguments[0]->assertColor(compiler, Strings::color); + const Number* amount = arguments[1]->assertNumber(compiler, Strings::amount); + double nr = amount->assertRange(0.0, 100.0, amount, compiler, Strings::amount); + ColorHslaObj copy(color->copyAsHSLA()); // Must make a copy! + copy->s(clamp(copy->s() - nr, 0.0, 100.0)); + return copy.detach(); // Return HSLA + } + + + BUILT_IN_FN(noFadeIn) + { + throw Exception::DeprecatedColorAdjustFn(compiler, + arguments, "fade-in", "$alpha: "); + } + + BUILT_IN_FN(noFadeOut) + { + throw Exception::DeprecatedColorAdjustFn(compiler, + arguments, "fade-out", "$alpha: -"); + } + + BUILT_IN_FN(noTansparentize) + { + throw Exception::DeprecatedColorAdjustFn(compiler, + arguments, "transparentize", "$alpha: -"); + } + + + BUILT_IN_FN(noSaturate) + { + throw Exception::DeprecatedColorAdjustFn(compiler, + arguments, "saturate", "$saturation: "); + } + + BUILT_IN_FN(noDesaturate) + { + throw Exception::DeprecatedColorAdjustFn(compiler, + arguments, "desaturate", "$saturation: -"); + } + + /*******************************************************************/ + + BUILT_IN_FN(opacify) + { + const Color* color = arguments[0]->assertColor(compiler, Strings::color); + const Number* amount = arguments[1]->assertNumber(compiler, Strings::amount); + double nr = amount->assertRange(0.0, 1.0, unit_none, compiler, Strings::amount); + ColorHslaObj copy(color->copyAsHSLA()); // Must make a copy! + copy->a(clamp(copy->a() + nr, 0.0, 1.0)); + return copy.detach(); // Return HSLA + } + + BUILT_IN_FN(transparentize) + { + const Color* color = arguments[0]->assertColor(compiler, Strings::color); + const Number* amount = arguments[1]->assertNumber(compiler, Strings::amount); + double nr = amount->assertRange(0.0, 1.0, unit_none, compiler, Strings::amount); + ColorHslaObj copy(color->copyAsHSLA()); // Must make a copy! + copy->a(clamp(copy->a() - nr, 0.0, 1.0)); + return copy.detach(); // Return HSLA + } + + BUILT_IN_FN(noOpacify) + { + throw Exception::DeprecatedColorAdjustFn(compiler, + arguments, "opacify", "$alpha: "); + } + + BUILT_IN_FN(noTransparentize) + { + throw Exception::DeprecatedColorAdjustFn(compiler, + arguments, "transparentize", "$alpha: -"); + } + + /*******************************************************************/ + + BUILT_IN_FN(alphaOne) + { + if (String * string = arguments[0]->isaString()) { + if (!string->hasQuotes() && isMsFilterStart(string->value())) { + return getFunctionString(Strings::alpha, pstate, arguments); + } + } + const Color* color = arguments[0]->assertColor(compiler, Strings::color); + return SASS_MEMORY_NEW(Number, pstate, color->a()); + } + + BUILT_IN_FN(alphaAny) + { + size_t size = arguments[0]->lengthAsList(); + + if (size == 0) { + throw Exception::MissingArgument(compiler, Keys::color); + } + + bool isOnlyIeFilters = true; + for (Value* value : arguments[0]->start()) { + if (String* string = value->isaString()) { + if (!isMsFilterStart(string->value())) { + isOnlyIeFilters = false; + break; + } + } + else { + isOnlyIeFilters = false; + break; + } + } + if (isOnlyIeFilters) { + // Support the proprietary Microsoft alpha() function. + return getFunctionString(Strings::alpha, pstate, arguments); + } + callStackFrame csf(compiler, arguments[0]->pstate()); + throw Exception::TooManyArguments(compiler, size, 1); + } + + + BUILT_IN_FN(opacity) + { + // Gracefully handle if number is passed + if (arguments[0]->isaNumber() || isSpecialNumber(arguments[0])) { + return getFunctionString("opacity", + pstate, arguments); + } + const Color* color = arguments[0]->assertColor(compiler, Strings::color); + return SASS_MEMORY_NEW(Number, pstate, color->a()); + } + + BUILT_IN_FN(noGrayscale) + { + if (arguments[0]->isaNumber()) { + compiler.addWarning("Passing a number to " + "color.grayscale() is deprecated.\n\nRecommendation: " + "grayscale(" + arguments[0]->inspect() + ")", + arguments[0]->pstate(), + Logger::WARN_NUMBER_ARG); + } + return grayscale(pstate, arguments, compiler, eval); + } + + + + BUILT_IN_FN(noOpacity) + { + if (arguments[0]->isaNumber()) { + compiler.addWarning("Passing a number to " + "color.opacity() is deprecated.\n\nRecommendation: " + "opacity(" + arguments[0]->inspect() + ")", + arguments[0]->pstate(), + Logger::WARN_NUMBER_ARG); + } + return opacity(pstate, arguments, compiler, eval); + } + + BUILT_IN_FN(ieHexStr) + { + const Color* color = arguments[0]->assertColor(compiler, Strings::color); + ColorRgbaObj rgba = color->toRGBA(); // This might create a copy + // clamp should not be needed here + double r = clamp(rgba->r(), 0.0, 255.0); + double g = clamp(rgba->g(), 0.0, 255.0); + double b = clamp(rgba->b(), 0.0, 255.0); + double a = clamp(rgba->a(), 0.0, 1.0) * 255.0; + sass::sstream ss; + ss << '#' << std::setw(2) << std::setfill('0') << std::uppercase; + ss << std::hex << std::setw(2) << fuzzyRound(a, compiler.epsilon); + ss << std::hex << std::setw(2) << fuzzyRound(r, compiler.epsilon); + ss << std::hex << std::setw(2) << fuzzyRound(g, compiler.epsilon); + ss << std::hex << std::setw(2) << fuzzyRound(b, compiler.epsilon); + return SASS_MEMORY_NEW(String, pstate, ss.str()); + } + + Number* getKwdArg(ValueFlatMap& keywords, const EnvKey& name, Logger& logger) + { + EnvKey variable(name.norm()); + auto kv = keywords.find(variable); + // Return null since args are optional + if (kv == keywords.end()) return nullptr; + // Get the number object from found keyword + Number* num = kv->second->assertNumber(logger, name.orig()); + // Only consume keyword once + keywords.erase(kv); + // Return the number + return num; + } + + + BUILT_IN_FN(adjust) + { + const Color* color = arguments[0]->assertColor(compiler, Strings::color); + ArgumentList* argumentList = arguments[1] + ->assertArgumentList(compiler, "kwargs"); + if (!argumentList->empty()) { + SourceSpan span(color->pstate()); + callStackFrame frame(compiler, BackTrace( + span, Strings::colorAdjust)); + throw Exception::RuntimeException(compiler, + "Only one positional argument is allowed. All " + "other arguments must be passed by name."); + } + + // ToDo: solve without erase ... + ValueFlatMap& keywords(argumentList->keywords()); + + Number* nr_r = getKwdArg(keywords, key_red, compiler); + Number* nr_g = getKwdArg(keywords, key_green, compiler); + Number* nr_b = getKwdArg(keywords, key_blue, compiler); + Number* nr_h = getKwdArg(keywords, key_hue, compiler); + Number* nr_s = getKwdArg(keywords, key_saturation, compiler); + Number* nr_l = getKwdArg(keywords, key_lightness, compiler); + Number* nr_a = getKwdArg(keywords, key_alpha, compiler); + Number* nr_wn = getKwdArg(keywords, key_whiteness, compiler); + Number* nr_bn = getKwdArg(keywords, key_blackness, compiler); + + if (nr_h) checkAngle(compiler, nr_h, Strings::hue); + if (nr_s) nr_s->checkPercent(compiler, Strings::saturation); + if (nr_l) nr_l->checkPercent(compiler, Strings::lightness); + + double r = nr_r ? nr_r->assertRange(-255.0, 255.0, unit_none, compiler, Strings::red) : 0.0; + double g = nr_g ? nr_g->assertRange(-255.0, 255.0, unit_none, compiler, Strings::green) : 0.0; + double b = nr_b ? nr_b->assertRange(-255.0, 255.0, unit_none, compiler, Strings::blue) : 0.0; + double s = nr_s ? nr_s->assertRange(-100.0, 100.0, unit_percent, compiler, Strings::saturation) : 0.0; + double l = nr_l ? nr_l->assertRange(-100.0, 100.0, unit_percent, compiler, Strings::lightness) : 0.0; + + double wn = nr_wn ? nr_wn->assertHasUnits(compiler, Strings::percent, Strings::whiteness)->assertRange(-100.0, 100.0, nr_wn, compiler, Strings::whiteness) : 0.0; + double bn = nr_bn ? nr_bn->assertHasUnits(compiler, Strings::percent, Strings::blackness)->assertRange(-100.0, 100.0, nr_bn, compiler, Strings::blackness) : 0.0; + + double a = nr_a ? nr_a->assertRange(-1.0, 1.0, nr_a, compiler, Strings::alpha) : 0.0; + + double h = nr_h ? coerceToDeg(nr_h) : 0.0; // Hue is a very special case + + if (!keywords.empty()) { + throw Exception::UnknownNamedArgument(compiler, keywords); + } + + bool hasRgb = nr_r || nr_g || nr_b; + bool hasHsl = nr_s || nr_l; + bool hasHwb = nr_wn || nr_bn; + bool hasHue = nr_h != nullptr; + + if (hasRgb && hasHsl && hasHwb) throw Exception::MixedParamGroups(compiler, "RGB", { "HSL", "HWB" }); + else if (hasRgb && hasHue) throw Exception::MixedParamGroups(compiler, "RGB", { "HSL/HWB" }); + else if (hasRgb && hasHsl) throw Exception::MixedParamGroups(compiler, "RGB", { "HSL" }); + else if (hasRgb && hasHwb) throw Exception::MixedParamGroups(compiler, "RGB", { "HWB" }); + else if (hasHsl && hasHwb) throw Exception::MixedParamGroups(compiler, "HSL", { "HWB" }); + else if (hasHwb && hasHsl) throw Exception::MixedParamGroups(compiler, "HSL", { "HWB" }); + + if (hasRgb) { + ColorRgbaObj rgba = color->copyAsRGBA(); + if (nr_r) rgba->r(clamp(rgba->r() + r, 0.0, 255.0)); + if (nr_g) rgba->g(clamp(rgba->g() + g, 0.0, 255.0)); + if (nr_b) rgba->b(clamp(rgba->b() + b, 0.0, 255.0)); + if (nr_a) rgba->a(clamp(rgba->a() + a, 0.0, 1.0)); + return rgba.detach(); + } + else if (hasHsl) { + ColorHslaObj hsla = color->copyAsHSLA(); + if (nr_h) hsla->h(absmod(hsla->h() + h, 360.0)); + if (nr_s) hsla->s(clamp(hsla->s() + s, 0.0, 100.0)); + if (nr_l) hsla->l(clamp(hsla->l() + l, 0.0, 100.0)); + if (nr_a) hsla->a(clamp(hsla->a() + a, 0.0, 1.0)); + return hsla.detach(); + } else if (hasHwb || nr_h) { // hue can be shared! + ColorHwbaObj hwba = color->copyAsHWBA(); + if (nr_h) hwba->h(absmod(hwba->h() + h, 360.0)); + if (nr_wn) hwba->w(clamp(hwba->w() + wn, 0.0, 100.0)); + if (nr_bn) hwba->b(clamp(hwba->b() + bn, 0.0, 100.0)); + if (nr_a) hwba->a(clamp(hwba->a() + a, 0.0, 1.0)); + return hwba.detach(); + } + else if (nr_a) { + ColorObj copy = SASS_MEMORY_COPY(color); + if (nr_a) copy->a(clamp(copy->a() + a, 0.0, 1.0)); + return copy.detach(); + } + return arguments[0]; + } + + BUILT_IN_FN(change) + { + const Color* color = arguments[0]->assertColor(compiler, Strings::color); + ArgumentList* argumentList = arguments[1] + ->assertArgumentList(compiler, "kwargs"); + if (!argumentList->empty()) { + SourceSpan span(color->pstate()); + callStackFrame frame(compiler, BackTrace( + span, Strings::colorChange)); + throw Exception::RuntimeException(compiler, + "Only one positional argument is allowed. All " + "other arguments must be passed by name."); + } + + // ToDo: solve without erase ... + ValueFlatMap& keywords(argumentList->keywords()); + + Number* nr_r = getKwdArg(keywords, key_red, compiler); + Number* nr_g = getKwdArg(keywords, key_green, compiler); + Number* nr_b = getKwdArg(keywords, key_blue, compiler); + Number* nr_h = getKwdArg(keywords, key_hue, compiler); + Number* nr_s = getKwdArg(keywords, key_saturation, compiler); + Number* nr_l = getKwdArg(keywords, key_lightness, compiler); + Number* nr_a = getKwdArg(keywords, key_alpha, compiler); + Number* nr_wn = getKwdArg(keywords, key_whiteness, compiler); + Number* nr_bn = getKwdArg(keywords, key_blackness, compiler); + + if (nr_h) checkAngle(compiler, nr_h, Strings::hue); + + double r = nr_r ? nr_r->assertRange(0.0, 255.0, unit_none, compiler, Strings::red) : 0.0; + double g = nr_g ? nr_g->assertRange(0.0, 255.0, unit_none, compiler, Strings::green) : 0.0; + double b = nr_b ? nr_b->assertRange(0.0, 255.0, unit_none, compiler, Strings::blue) : 0.0; + double s = nr_s ? nr_s->checkPercent(compiler, Strings::saturation)->assertRange(0.0, 100.0, unit_percent, compiler, Strings::saturation) : 0.0; + double l = nr_l ? nr_l->checkPercent(compiler, Strings::lightness)->assertRange(0.0, 100.0, unit_percent, compiler, Strings::lightness) : 0.0; + double a = nr_a ? nr_a->assertRange(0.0, 1.0, nr_a, compiler, Strings::alpha) : 0.0; + double wn = nr_wn ? nr_wn->assertHasUnits(compiler, Strings::percent, Strings::whiteness)->assertRange(0.0, 100.0, nr_wn, compiler, Strings::whiteness) : 0.0; + double bn = nr_bn ? nr_bn->assertHasUnits(compiler, Strings::percent, Strings::blackness)->assertRange( 0.0, 100.0, nr_bn, compiler, Strings::blackness) : 0.0; + double h = nr_h ? coerceToDeg(nr_h) : 0.0; // Hue is a very special case + + if (!keywords.empty()) { + throw Exception::UnknownNamedArgument(compiler, keywords); + } + + bool hasRgb = nr_r || nr_g || nr_b; + bool hasHsl = nr_s || nr_l; + bool hasHwb = nr_wn || nr_bn; + bool hasHue = nr_h; + + if (hasRgb && hasHsl && hasHwb) throw Exception::MixedParamGroups(compiler, "RGB", { "HSL", "HWB" }); + else if (hasRgb && hasHue) throw Exception::MixedParamGroups(compiler, "RGB", { "HSL/HWB" }); + else if (hasRgb && hasHsl) throw Exception::MixedParamGroups(compiler, "RGB", { "HSL" }); + else if (hasRgb && hasHwb) throw Exception::MixedParamGroups(compiler, "RGB", { "HWB" }); + else if (hasHsl && hasHwb) throw Exception::MixedParamGroups(compiler, "HSL", { "HWB" }); + else if (hasHwb && hasHsl) throw Exception::MixedParamGroups(compiler, "HSL", { "HWB" }); + + if (hasRgb) { + ColorRgbaObj rgba = color->copyAsRGBA(); + if (nr_r) rgba->r(clamp(r, 0.0, 255.0)); + if (nr_g) rgba->g(clamp(g, 0.0, 255.0)); + if (nr_b) rgba->b(clamp(b, 0.0, 255.0)); + if (nr_a) rgba->a(clamp(a, 0.0, 1.0)); + return rgba.detach(); + } + else if (hasHsl) { + ColorHslaObj hsla = color->copyAsHSLA(); + if (nr_h) hsla->h(absmod(h, 360.0)); + if (nr_s) hsla->s(clamp(s, 0.0, 100.0)); + if (nr_l) hsla->l(clamp(l, 0.0, 100.0)); + if (nr_a) hsla->a(clamp(a, 0.0, 1.0)); + return hsla.detach(); + } + else if (hasHwb || nr_h) { // hue can be shared! + ColorHwbaObj hwba = color->copyAsHWBA(); + if (nr_h) hwba->h(absmod(h, 360.0)); + if (nr_wn) hwba->w(clamp(wn, 0.0, 100.0)); + if (nr_bn) hwba->b(clamp(bn, 0.0, 100.0)); + if (nr_a) hwba->a(clamp(a, 0.0, 1.0)); + return hwba.detach(); + } + else if (nr_a) { + ColorObj copy = SASS_MEMORY_COPY(color); + if (nr_a) copy->a(clamp(a, 0.0, 1.0)); + return copy.detach(); + } + return arguments[0]; + } + + BUILT_IN_FN(scale) + { + const Color* color = arguments[0]->assertColor(compiler, Strings::color); + ArgumentList* argumentList = arguments[1] + ->assertArgumentList(compiler, "kwargs"); + if (!argumentList->empty()) { + SourceSpan span(color->pstate()); + callStackFrame frame(compiler, BackTrace( + span, Strings::scaleColor)); + throw Exception::RuntimeException(compiler, + "Only one positional argument is allowed. All " + "other arguments must be passed by name."); + } + + // ToDo: solve without erase ... + ValueFlatMap& keywords(argumentList->keywords()); + + Number* nr_r = getKwdArg(keywords, key_red, compiler); + Number* nr_g = getKwdArg(keywords, key_green, compiler); + Number* nr_b = getKwdArg(keywords, key_blue, compiler); + Number* nr_s = getKwdArg(keywords, key_saturation, compiler); + Number* nr_l = getKwdArg(keywords, key_lightness, compiler); + Number* nr_wn = getKwdArg(keywords, key_whiteness, compiler); + Number* nr_bn = getKwdArg(keywords, key_blackness, compiler); + Number* nr_a = getKwdArg(keywords, key_alpha, compiler); + + double r = nr_r ? nr_r->assertHasUnits(compiler, Strings::percent, Strings::red)->assertRange(-100.0, 100.0, nr_r, compiler, Strings::red) / 100.0 : 0.0; + double g = nr_g ? nr_g->assertHasUnits(compiler, Strings::percent, Strings::green)->assertRange(-100.0, 100.0, nr_g, compiler, Strings::green) / 100.0 : 0.0; + double b = nr_b ? nr_b->assertHasUnits(compiler, Strings::percent, Strings::blue)->assertRange(-100.0, 100.0, nr_b, compiler, Strings::blue) / 100.0 : 0.0; + double s = nr_s ? nr_s->assertHasUnits(compiler, Strings::percent, Strings::saturation)->assertRange(-100.0, 100.0, nr_s, compiler, Strings::saturation) / 100.0 : 0.0; + double l = nr_l ? nr_l->assertHasUnits(compiler, Strings::percent, Strings::lightness)->assertRange(-100.0, 100.0, nr_l, compiler, Strings::lightness) / 100.0 : 0.0; + double wn = nr_wn ? nr_wn->assertHasUnits(compiler, Strings::percent, Strings::whiteness)->assertRange(-100.0, 100.0, nr_wn, compiler, Strings::whiteness) / 100.0 : 0.0; + double bn = nr_bn ? nr_bn->assertHasUnits(compiler, Strings::percent, Strings::blackness)->assertRange(-100.0, 100.0, nr_bn, compiler, Strings::blackness) / 100.0 : 0.0; + double a = nr_a ? nr_a->assertHasUnits(compiler, Strings::percent, Strings::alpha)->assertRange(-100.0, 100.0, nr_a, compiler, Strings::alpha) / 100.0 : 0.0; + + if (!keywords.empty()) { + throw Exception::UnknownNamedArgument(compiler, keywords); + } + + bool hasRgb = nr_r || nr_g || nr_b; + bool hasHsl = nr_s || nr_l; + bool hasHwb = nr_wn || nr_bn; + + if (hasRgb && hasHsl && hasHwb) throw Exception::MixedParamGroups(compiler, "RGB", { "HSL", "HWB" }); + else if (hasRgb && hasHsl) throw Exception::MixedParamGroups(compiler, "RGB", { "HSL" }); + else if (hasRgb && hasHwb) throw Exception::MixedParamGroups(compiler, "RGB", { "HWB" }); + else if (hasHsl && hasHwb) throw Exception::MixedParamGroups(compiler, "HSL", { "HWB" }); + else if (hasHwb && hasHsl) throw Exception::MixedParamGroups(compiler, "HSL", { "HWB" }); + + if (hasRgb) { + ColorRgbaObj rgba = color->copyAsRGBA(); + if (nr_r) rgba->r(scaleValue(rgba->r(), r, 255.0)); + if (nr_g) rgba->g(scaleValue(rgba->g(), g, 255.0)); + if (nr_b) rgba->b(scaleValue(rgba->b(), b, 255.0)); + if (nr_a) rgba->a(scaleValue(rgba->a(), a, 1.0)); + return rgba.detach(); + } + else if (hasHsl) { + ColorHslaObj hsla = color->copyAsHSLA(); + if (nr_s) hsla->s(scaleValue(hsla->s(), s, 100.0)); + if (nr_l) hsla->l(scaleValue(hsla->l(), l, 100.0)); + if (nr_a) hsla->a(scaleValue(hsla->a(), a, 1.0)); + return hsla.detach(); + } + else if (hasHwb) { // hue can be shared! + ColorHwbaObj hwba = color->copyAsHWBA(); + if (nr_wn) hwba->w(scaleValue(hwba->w(), wn, 100.0)); + if (nr_bn) hwba->b(scaleValue(hwba->b(), bn, 100.0)); + if (nr_a) hwba->a(scaleValue(hwba->a(), a, 1.0)); + return hwba.detach(); + } + else if (nr_a) { + ColorObj copy = SASS_MEMORY_COPY(color); + if (nr_a) copy->a(scaleValue(copy->a(), a, 1.0)); + return copy.detach(); + } + return arguments[0]; + } + + BUILT_IN_FN(mix) + { + const Color* color1 = arguments[0]->assertColor(compiler, "color1"); + const Color* color2 = arguments[1]->assertColor(compiler, "color2"); + const Number* weight = arguments[2]->assertNumber(compiler, "weight"); + return mixColors(color1, color2, weight, pstate, compiler); + } + + /*******************************************************************/ + + void registerFunctions(Compiler& ctx) + { + + // Some functions are stricter if called from module namespace + uint32_t idx_rgb_strict = ctx.createBuiltInOverloadFns(key_rgb, { + std::make_pair("$red, $green, $blue, $alpha", fnRgb4arg), + std::make_pair("$red, $green, $blue", fnRgb3arg), + std::make_pair("$color, $alpha", fnRgb2arg), + std::make_pair("$channels", fnRgb1arg), + }); + uint32_t idx_rgb_loose = ctx.createBuiltInOverloadFns(key_rgb, { + std::make_pair("$red, $green, $blue, $alpha", rgb4arg), + std::make_pair("$red, $green, $blue", rgb3arg), + std::make_pair("$color, $alpha", rgb2arg), + std::make_pair("$channels", rgb1arg), + }); + uint32_t idx_rgba_strict = ctx.createBuiltInOverloadFns(key_rgba, { + std::make_pair("$red, $green, $blue, $alpha", fnRgba4arg), + std::make_pair("$red, $green, $blue", fnRgba3arg), + std::make_pair("$color, $alpha", fnRgba2arg), + std::make_pair("$channels", fnRgba1arg), + }); + uint32_t idx_rgba_loose = ctx.createBuiltInOverloadFns(key_rgba, { + std::make_pair("$red, $green, $blue, $alpha", rgba4arg), + std::make_pair("$red, $green, $blue", rgba3arg), + std::make_pair("$color, $alpha", rgba2arg), + std::make_pair("$channels", rgba1arg), + }); + uint32_t idx_hsl_strict = ctx.createBuiltInOverloadFns(key_hsl, { + std::make_pair("$hue, $saturation, $lightness, $alpha", fnHsl4arg), + std::make_pair("$hue, $saturation, $lightness", fnHsl3arg), + std::make_pair("$hue, $saturation", fnHsl2arg), + std::make_pair("$channels", fnHsl1arg), + }); + uint32_t idx_hsl_loose = ctx.createBuiltInOverloadFns(key_hsl, { + std::make_pair("$hue, $saturation, $lightness, $alpha", hsl4arg), + std::make_pair("$hue, $saturation, $lightness", hsl3arg), + std::make_pair("$hue, $saturation", hsl2arg), + std::make_pair("$channels", hsl1arg), + }); + uint32_t idx_hsla_strict = ctx.createBuiltInOverloadFns(key_hsla, { + std::make_pair("$hue, $saturation, $lightness, $alpha", fnHsla4arg), + std::make_pair("$hue, $saturation, $lightness", fnHsla3arg), + std::make_pair("$hue, $saturation", fnHsla2arg), + std::make_pair("$channels", fnHsla1arg), + }); + uint32_t idx_hsla_loose = ctx.createBuiltInOverloadFns(key_hsla, { + std::make_pair("$hue, $saturation, $lightness, $alpha", hsla4arg), + std::make_pair("$hue, $saturation, $lightness", hsla3arg), + std::make_pair("$hue, $saturation", hsla2arg), + std::make_pair("$channels", hsla1arg), + }); + uint32_t idx_hwb_strict = ctx.createBuiltInOverloadFns(key_hwb, { + std::make_pair("$hue, $whiteness, $blackness, $alpha: 1", fnHwb4arg), + // std::make_pair("$hue, $whiteness, $blackness", fnHwb3arg), + // std::make_pair("$color, $alpha", fnHwb2arg), + std::make_pair("$channels", fnHwb1arg), + }); + uint32_t idx_hwb_loose = ctx.createBuiltInOverloadFns(key_hwb, { + std::make_pair("$hue, $whiteness, $blackness, $alpha: 1", hwb4arg), + // std::make_pair("$hue, $whiteness, $blackness", hwb3arg), + // std::make_pair("$color, $alpha", hwb2arg), + std::make_pair("$channels", hwb1arg), + }); + // uint32_t idx_hwba_strict = ctx.createBuiltInOverloadFns(key_hwba, { + // std::make_pair("$hue, $whiteness, $blackness, $alpha", fnHwba4arg), + // std::make_pair("$hue, $whiteness, $blackness", fnHwba3arg), + // std::make_pair("$color, $alpha", fnHwba2arg), + // std::make_pair("$channels", fnHwba1arg), + // }); + // uint32_t idx_hwba_loose = ctx.createBuiltInOverloadFns(key_hwba, { + // std::make_pair("$hue, $whiteness, $blackness, $alpha", hwba4arg), + // std::make_pair("$hue, $whiteness, $blackness", hwba3arg), + // std::make_pair("$color, $alpha", hwba2arg), + // std::make_pair("$channels", hwba1arg), + // }); + + uint32_t idx_red = ctx.createBuiltInFunction(key_red, "$color", red); + uint32_t idx_green = ctx.createBuiltInFunction(key_green, "$color", green); + uint32_t idx_blue = ctx.createBuiltInFunction(key_blue, "$color", blue); + uint32_t idx_hue = ctx.createBuiltInFunction(key_hue, "$color", hue); + uint32_t idx_lightness = ctx.createBuiltInFunction(key_lightness, "$color", lightness); + uint32_t idx_saturation = ctx.createBuiltInFunction(key_saturation, "$color", saturation); + uint32_t idx_blackness = ctx.createBuiltInFunction(key_blackness, "$color", blackness); + uint32_t idx_whiteness = ctx.createBuiltInFunction(key_whiteness, "$color", whiteness); + uint32_t idx_invert_strict = ctx.createBuiltInFunction(key_invert, "$color, $weight: 100%", fnInvert); + uint32_t idx_invert_loose = ctx.createBuiltInFunction(key_invert, "$color, $weight: 100%", invert); + uint32_t idx_grayscale_strict = ctx.createBuiltInFunction(key_grayscale, "$color", noGrayscale); + uint32_t idx_grayscale_loose = ctx.createBuiltInFunction(key_grayscale, "$color", grayscale); + uint32_t idx_complement = ctx.createBuiltInFunction(key_complement, "$color", complement); + uint32_t idx_desaturate_strict = ctx.createBuiltInFunction(key_desaturate, "$color, $amount", noDesaturate); + uint32_t idx_desaturate_loose = ctx.createBuiltInFunction(key_desaturate, "$color, $amount", desaturate); + uint32_t idx_saturate_strict = ctx.createBuiltInFunction(key_saturate, "$color, $amount", noSaturate); + uint32_t idx_saturate_loose = ctx.createBuiltInOverloadFns(key_saturate, { + std::make_pair("$amount", saturate1arg), + std::make_pair("$color, $amount", saturate2arg), + }); + uint32_t idx_lighten_strict = ctx.createBuiltInFunction(key_lighten, "$color, $amount", noLighten); + uint32_t idx_lighten_loose = ctx.createBuiltInFunction(key_lighten, "$color, $amount", lighten); + uint32_t idx_darken_strict = ctx.createBuiltInFunction(key_darken, "$color, $amount", noDarken); + uint32_t idx_darken_loose = ctx.createBuiltInFunction(key_darken, "$color, $amount", darken); + uint32_t idx_adjust_hue_strict = ctx.createBuiltInFunction(key_adjust_hue, "$color, $degrees", noAdjustHue); + uint32_t idx_adjust_hue_loose = ctx.createBuiltInFunction(key_adjust_hue, "$color, $degrees", adjustHue); + uint32_t idx_adjust = ctx.registerBuiltInFunction(key_adjust_color, "$color, $kwargs...", adjust); + uint32_t idx_change = ctx.registerBuiltInFunction(key_change_color, "$color, $kwargs...", change); + uint32_t idx_scale = ctx.registerBuiltInFunction(key_scale_color, "$color, $kwargs...", scale); + uint32_t idx_mix = ctx.registerBuiltInFunction(key_mix, "$color1, $color2, $weight: 50%", mix); + uint32_t idx_opacify_strict = ctx.createBuiltInFunction(key_opacify, "$color, $amount", noOpacify); + uint32_t idx_opacify_loose = ctx.createBuiltInFunction(key_opacify, "$color, $amount", opacify); + uint32_t idx_fade_in_strict = ctx.createBuiltInFunction(key_fade_in, "$color, $amount", noFadeIn); + uint32_t idx_fade_in_loose = ctx.createBuiltInFunction(key_fade_in, "$color, $amount", opacify); + uint32_t idx_fade_out_strict = ctx.createBuiltInFunction(key_fade_out, "$color, $amount", noFadeOut); + uint32_t idx_fade_out_loose = ctx.createBuiltInFunction(key_fade_out, "$color, $amount", transparentize); + uint32_t idx_transparentize_strict = ctx.createBuiltInFunction(key_transparentize, "$color, $amount", noTansparentize); + uint32_t idx_transparentize_loose = ctx.createBuiltInFunction(key_transparentize, "$color, $amount", transparentize); + uint32_t idx_ie_hex_str = ctx.createBuiltInFunction(key_ie_hex_str, "$color", ieHexStr); + uint32_t idx_alpha = ctx.createBuiltInOverloadFns(key_alpha, { + std::make_pair("$color", alphaOne), + std::make_pair("$args...", alphaAny), + }); + uint32_t idx_opacity_strict = ctx.createBuiltInFunction(key_opacity, "$color", noOpacity); + uint32_t idx_opacity_loose = ctx.createBuiltInFunction(key_opacity, "$color", opacity); + + ctx.exposeFunction(key_rgb, idx_rgb_loose); + ctx.exposeFunction(key_rgba, idx_rgba_loose); + ctx.exposeFunction(key_hsl, idx_hsl_loose); + ctx.exposeFunction(key_hsla, idx_hsla_loose); + ctx.exposeFunction(key_hwb, idx_hwb_loose); + // ctx.exposeFunction(key_hwba, idx_hwba_loose); + ctx.exposeFunction(key_red, idx_red); + ctx.exposeFunction(key_green, idx_green); + ctx.exposeFunction(key_blue, idx_blue); + ctx.exposeFunction(key_hue, idx_hue); + ctx.exposeFunction(key_lightness, idx_lightness); + ctx.exposeFunction(key_saturation, idx_saturation); + ctx.exposeFunction(key_blackness, idx_blackness); + ctx.exposeFunction(key_whiteness, idx_whiteness); + ctx.exposeFunction(key_invert, idx_invert_loose); + ctx.exposeFunction(key_grayscale, idx_grayscale_loose); + ctx.exposeFunction(key_complement, idx_complement); + ctx.exposeFunction(key_desaturate, idx_desaturate_loose); + ctx.exposeFunction(key_saturate, idx_saturate_loose); + ctx.exposeFunction(key_lighten, idx_lighten_loose); + ctx.exposeFunction(key_darken, idx_darken_loose); + ctx.exposeFunction(key_adjust_hue, idx_adjust_hue_loose); + ctx.exposeFunction(key_adjust_color, idx_adjust); + ctx.exposeFunction(key_change_color, idx_change); + ctx.exposeFunction(key_scale_color, idx_scale); + ctx.exposeFunction(key_mix, idx_mix); + ctx.exposeFunction(key_opacify, idx_opacify_loose); + ctx.exposeFunction(key_fade_in, idx_fade_in_loose); + ctx.exposeFunction(key_fade_out, idx_fade_out_loose); + ctx.exposeFunction(key_transparentize, idx_transparentize_loose); + ctx.exposeFunction(key_ie_hex_str, idx_ie_hex_str); + ctx.exposeFunction(key_alpha, idx_alpha); + ctx.exposeFunction(key_opacity, idx_opacity_loose); + + BuiltInMod& module(ctx.createModule("color")); + module.addFunction(key_rgb, idx_rgb_strict); + module.addFunction(key_rgba, idx_rgba_strict); + module.addFunction(key_hsl, idx_hsl_strict); + module.addFunction(key_hsla, idx_hsla_strict); + module.addFunction(key_hwb, idx_hwb_strict); + // module.addFunction(key_hwba, idx_hwba_strict); + module.addFunction(key_red, idx_red); + module.addFunction(key_green, idx_green); + module.addFunction(key_blue, idx_blue); + module.addFunction(key_hue, idx_hue); + module.addFunction(key_lightness, idx_lightness); + module.addFunction(key_saturation, idx_saturation); + module.addFunction(key_blackness, idx_blackness); + module.addFunction(key_whiteness, idx_whiteness); + module.addFunction(key_invert, idx_invert_strict); + module.addFunction(key_grayscale, idx_grayscale_strict); + module.addFunction(key_complement, idx_complement); + module.addFunction(key_desaturate, idx_desaturate_strict); + module.addFunction(key_saturate, idx_saturate_strict); + module.addFunction(key_lighten, idx_lighten_strict); + module.addFunction(key_darken, idx_darken_strict); + module.addFunction(key_adjust_hue, idx_adjust_hue_strict); + module.addFunction(key_adjust, idx_adjust); + module.addFunction(key_change, idx_change); + module.addFunction(key_scale, idx_scale); + module.addFunction(key_mix, idx_mix); + module.addFunction(key_opacify, idx_opacify_strict); + module.addFunction(key_fade_in, idx_fade_in_strict); + module.addFunction(key_fade_out, idx_fade_out_strict); + module.addFunction(key_transparentize, idx_transparentize_strict); + module.addFunction(key_ie_hex_str, idx_ie_hex_str); + module.addFunction(key_alpha, idx_alpha); + module.addFunction(key_opacity, idx_opacity_strict); + + } - Signature adjust_color_sig = "adjust-color($color, $red: false, $green: false, $blue: false, $hue: false, $saturation: false, $lightness: false, $alpha: false)"; - BUILT_IN(adjust_color) - { - Color* col = ARG("$color", Color); - Number* r = Cast(env["$red"]); - Number* g = Cast(env["$green"]); - Number* b = Cast(env["$blue"]); - Number* h = Cast(env["$hue"]); - Number* s = Cast(env["$saturation"]); - Number* l = Cast(env["$lightness"]); - Number* a = Cast(env["$alpha"]); - - bool rgb = r || g || b; - bool hsl = h || s || l; - - if (rgb && hsl) { - error("Cannot specify HSL and RGB values for a color at the same time for `adjust-color'", pstate, traces); - } - else if (rgb) { - Color_RGBA_Obj c = col->copyAsRGBA(); - if (r) c->r(c->r() + DARG_R_BYTE("$red")); - if (g) c->g(c->g() + DARG_R_BYTE("$green")); - if (b) c->b(c->b() + DARG_R_BYTE("$blue")); - if (a) c->a(c->a() + DARG_R_FACT("$alpha")); - return c.detach(); - } - else if (hsl) { - Color_HSLA_Obj c = col->copyAsHSLA(); - if (h) c->h(c->h() + absmod(h->value(), 360.0)); - if (s) c->s(c->s() + DARG_R_PRCT("$saturation")); - if (l) c->l(c->l() + DARG_R_PRCT("$lightness")); - if (a) c->a(c->a() + DARG_R_FACT("$alpha")); - return c.detach(); - } - else if (a) { - Color_Obj c = SASS_MEMORY_COPY(col); - c->a(c->a() + DARG_R_FACT("$alpha")); - c->a(clip(c->a(), 0.0, 1.0)); - return c.detach(); - } - error("not enough arguments for `adjust-color'", pstate, traces); - // unreachable - return col; } - Signature scale_color_sig = "scale-color($color, $red: false, $green: false, $blue: false, $hue: false, $saturation: false, $lightness: false, $alpha: false)"; - BUILT_IN(scale_color) + /*******************************************************************/ + + Value* rgbFn(const sass::string& name, const ValueVector& arguments, const SourceSpan& pstate, Logger& logger, bool strict) { - Color* col = ARG("$color", Color); - Number* r = Cast(env["$red"]); - Number* g = Cast(env["$green"]); - Number* b = Cast(env["$blue"]); - Number* h = Cast(env["$hue"]); - Number* s = Cast(env["$saturation"]); - Number* l = Cast(env["$lightness"]); - Number* a = Cast(env["$alpha"]); - - bool rgb = r || g || b; - bool hsl = h || s || l; - - if (rgb && hsl) { - error("Cannot specify HSL and RGB values for a color at the same time for `scale-color'", pstate, traces); - } - else if (rgb) { - Color_RGBA_Obj c = col->copyAsRGBA(); - double rscale = (r ? DARG_R_PRCT("$red") : 0.0) / 100.0; - double gscale = (g ? DARG_R_PRCT("$green") : 0.0) / 100.0; - double bscale = (b ? DARG_R_PRCT("$blue") : 0.0) / 100.0; - double ascale = (a ? DARG_R_PRCT("$alpha") : 0.0) / 100.0; - if (rscale) c->r(c->r() + rscale * (rscale > 0.0 ? 255.0 - c->r() : c->r())); - if (gscale) c->g(c->g() + gscale * (gscale > 0.0 ? 255.0 - c->g() : c->g())); - if (bscale) c->b(c->b() + bscale * (bscale > 0.0 ? 255.0 - c->b() : c->b())); - if (ascale) c->a(c->a() + ascale * (ascale > 0.0 ? 1.0 - c->a() : c->a())); - return c.detach(); - } - else if (hsl) { - Color_HSLA_Obj c = col->copyAsHSLA(); - double hscale = (h ? DARG_R_PRCT("$hue") : 0.0) / 100.0; - double sscale = (s ? DARG_R_PRCT("$saturation") : 0.0) / 100.0; - double lscale = (l ? DARG_R_PRCT("$lightness") : 0.0) / 100.0; - double ascale = (a ? DARG_R_PRCT("$alpha") : 0.0) / 100.0; - if (hscale) c->h(c->h() + hscale * (hscale > 0.0 ? 360.0 - c->h() : c->h())); - if (sscale) c->s(c->s() + sscale * (sscale > 0.0 ? 100.0 - c->s() : c->s())); - if (lscale) c->l(c->l() + lscale * (lscale > 0.0 ? 100.0 - c->l() : c->l())); - if (ascale) c->a(c->a() + ascale * (ascale > 0.0 ? 1.0 - c->a() : c->a())); - return c.detach(); - } - else if (a) { - Color_Obj c = SASS_MEMORY_COPY(col); - double ascale = DARG_R_PRCT("$alpha") / 100.0; - c->a(c->a() + ascale * (ascale > 0.0 ? 1.0 - c->a() : c->a())); - c->a(clip(c->a(), 0.0, 1.0)); - return c.detach(); - } - error("not enough arguments for `scale-color'", pstate, traces); - // unreachable - return col; + Value* _r = arguments[0]; + Value* _g = arguments[1]; + Value* _b = arguments[2]; + Value* _a = nullptr; + if (arguments.size() > 3) { + _a = arguments[3]; + } + // Check if any `calc()` or `var()` are passed + if (!strict && (isSpecialNumber(_r) || isSpecialNumber(_g) || isSpecialNumber(_b) || isSpecialNumber(_a))) { + sass::sstream fncall; + fncall << name << "("; + fncall << _r->inspect() << ", "; + fncall << _g->inspect() << ", "; + fncall << _b->inspect(); + if (_a) { fncall << ", " << _a->inspect(); } + fncall << ")"; + return SASS_MEMORY_NEW(String, pstate, fncall.str()); + } + + Number* r = _r->assertNumber(logger, Strings::red); + Number* g = _g->assertNumber(logger, Strings::green); + Number* b = _b->assertNumber(logger, Strings::blue); + Number* a = _a ? _a->assertNumber(logger, Strings::alpha) : nullptr; + + return SASS_MEMORY_NEW(ColorRgba, pstate, + fuzzyRound(_percentageOrUnitless(r, 255, "$red", logger), logger.epsilon), + fuzzyRound(_percentageOrUnitless(g, 255, "$green", logger), logger.epsilon), + fuzzyRound(_percentageOrUnitless(b, 255, "$blue", logger), logger.epsilon), + _a ? _percentageOrUnitless(a, 1.0, "$alpha", logger) : 1.0, "", true); // Hmmm + } - Signature change_color_sig = "change-color($color, $red: false, $green: false, $blue: false, $hue: false, $saturation: false, $lightness: false, $alpha: false)"; - BUILT_IN(change_color) + /*******************************************************************/ + + Value* hwbFn(const sass::string& name, const ValueVector& arguments, const SourceSpan& pstate, Logger& logger, bool strict) { - Color* col = ARG("$color", Color); - Number* r = Cast(env["$red"]); - Number* g = Cast(env["$green"]); - Number* b = Cast(env["$blue"]); - Number* h = Cast(env["$hue"]); - Number* s = Cast(env["$saturation"]); - Number* l = Cast(env["$lightness"]); - Number* a = Cast(env["$alpha"]); - - bool rgb = r || g || b; - bool hsl = h || s || l; - - if (rgb && hsl) { - error("Cannot specify HSL and RGB values for a color at the same time for `change-color'", pstate, traces); - } - else if (rgb) { - Color_RGBA_Obj c = col->copyAsRGBA(); - if (r) c->r(DARG_U_BYTE("$red")); - if (g) c->g(DARG_U_BYTE("$green")); - if (b) c->b(DARG_U_BYTE("$blue")); - if (a) c->a(DARG_U_FACT("$alpha")); - return c.detach(); - } - else if (hsl) { - Color_HSLA_Obj c = col->copyAsHSLA(); - if (h) c->h(absmod(h->value(), 360.0)); - if (s) c->s(DARG_U_PRCT("$saturation")); - if (l) c->l(DARG_U_PRCT("$lightness")); - if (a) c->a(DARG_U_FACT("$alpha")); - return c.detach(); - } - else if (a) { - Color_Obj c = SASS_MEMORY_COPY(col); - c->a(clip(DARG_U_FACT("$alpha"), 0.0, 1.0)); - return c.detach(); - } - error("not enough arguments for `change-color'", pstate, traces); - // unreachable - return col; + Value* _h = arguments[0]; + Value* _w = arguments[1]; + Value* _b = arguments[2]; + Value* _a = nullptr; + if (arguments.size() > 3) { + _a = arguments[3]; + } + // Check if any `calc()` or `var()` are passed + if (!strict && (isSpecialNumber(_h) || isSpecialNumber(_w) || isSpecialNumber(_b) || isSpecialNumber(_a))) { + sass::sstream fncall; + fncall << name << "("; + fncall << _h->inspect() << ", "; + fncall << _w->inspect() << ", "; + fncall << _b->inspect(); + if (_a) { fncall << ", " << _a->inspect(); } + fncall << ")"; + return SASS_MEMORY_NEW(String, pstate, fncall.str()); + } + + Number* h = _h->assertNumber(logger, Strings::hue); + Number* w = _w->assertNumber(logger, Strings::whiteness) + ->assertHasUnits(logger, "%", Strings::whiteness); + Number* b = _b->assertNumber(logger, Strings::blackness) + ->assertHasUnits(logger, "%", Strings::blackness); + Number* a = _a ? _a->assertNumber(logger, Strings::alpha) : nullptr; + + return SASS_MEMORY_NEW(ColorHwba, pstate, + coerceToDeg(h), + w->assertRange(0.0, 100.0, w, logger, Strings::whiteness), + b->assertRange(0.0, 100.0, b, logger, Strings::blackness), + _a ? _percentageOrUnitless(a, 1.0, "$alpha", logger) : 1.0, "", true); + } - Signature ie_hex_str_sig = "ie-hex-str($color)"; - BUILT_IN(ie_hex_str) + /*******************************************************************/ + + Value* hslFn(const sass::string& name, const ValueVector& arguments, const SourceSpan& pstate, Logger& logger, bool strict) { - Color* col = ARG("$color", Color); - Color_RGBA_Obj c = col->toRGBA(); - double r = clip(c->r(), 0.0, 255.0); - double g = clip(c->g(), 0.0, 255.0); - double b = clip(c->b(), 0.0, 255.0); - double a = clip(c->a(), 0.0, 1.0) * 255.0; - - sass::ostream ss; - ss << '#' << std::setw(2) << std::setfill('0'); - ss << std::hex << std::setw(2) << static_cast(Sass::round(a, ctx.c_options.precision)); - ss << std::hex << std::setw(2) << static_cast(Sass::round(r, ctx.c_options.precision)); - ss << std::hex << std::setw(2) << static_cast(Sass::round(g, ctx.c_options.precision)); - ss << std::hex << std::setw(2) << static_cast(Sass::round(b, ctx.c_options.precision)); - - sass::string result = ss.str(); - Util::ascii_str_toupper(&result); - return SASS_MEMORY_NEW(String_Quoted, pstate, result); + Value* _h = arguments[0]; + Value* _s = arguments[1]; + Value* _l = arguments[2]; + Value* _a = nullptr; + if (arguments.size() > 3) { + _a = arguments[3]; + } + // Check if any `calc()` or `var()` are passed + if (!strict && (isSpecialNumber(_h) || isSpecialNumber(_s) || isSpecialNumber(_l) || isSpecialNumber(_a))) { + sass::sstream fncall; + fncall << name << "("; + fncall << _h->inspect() << ", "; + fncall << _s->inspect() << ", "; + fncall << _l->inspect(); + if (_a) { fncall << ", " << _a->inspect(); } + fncall << ")"; + return SASS_MEMORY_NEW(String, pstate, fncall.str()); + } + + Number* h = _h->assertNumber(logger, Strings::hue); + Number* s = _s->assertNumber(logger, Strings::saturation); + Number* l = _l->assertNumber(logger, Strings::lightness); + Number* a = _a ? _a->assertNumber(logger, Strings::alpha) : nullptr; + + checkAngle(logger, h, Strings::hue); + s->checkPercent(logger, Strings::saturation); + l->checkPercent(logger, Strings::lightness); + return SASS_MEMORY_NEW(ColorHsla, pstate, + coerceToDeg(h), + clamp(s->value(), 0.0, 100.0), + clamp(l->value(), 0.0, 100.0), + _a ? _percentageOrUnitless(a, 1.0, "$alpha", logger) : 1.0, "", true); } + /*******************************************************************/ + } } diff --git a/src/fn_colors.hpp b/src/fn_colors.hpp index a474c64b5b..22be61df5b 100644 --- a/src/fn_colors.hpp +++ b/src/fn_colors.hpp @@ -1,5 +1,12 @@ -#ifndef SASS_FN_COLORS_H -#define SASS_FN_COLORS_H +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#ifndef SASS_FN_COLORS_HPP +#define SASS_FN_COLORS_HPP + +// sass.hpp must go before all system headers +// to get the __EXTENSIONS__ fix on Solaris. +#include "capi_sass.hpp" #include "fn_utils.hpp" @@ -7,76 +14,42 @@ namespace Sass { namespace Functions { - // macros for common ranges (u mean unsigned or upper, r for full range) - #define DARG_U_FACT(argname) get_arg_r(argname, env, sig, pstate, traces, - 0.0, 1.0) // double - #define DARG_R_FACT(argname) get_arg_r(argname, env, sig, pstate, traces, - 1.0, 1.0) // double - #define DARG_U_BYTE(argname) get_arg_r(argname, env, sig, pstate, traces, - 0.0, 255.0) // double - #define DARG_R_BYTE(argname) get_arg_r(argname, env, sig, pstate, traces, - 255.0, 255.0) // double - #define DARG_U_PRCT(argname) get_arg_r(argname, env, sig, pstate, traces, - 0.0, 100.0) // double - #define DARG_R_PRCT(argname) get_arg_r(argname, env, sig, pstate, traces, - 100.0, 100.0) // double - - // macros for color related inputs (rbg and alpha/opacity values) - #define COLOR_NUM(argname) color_num(argname, env, sig, pstate, traces) // double - #define ALPHA_NUM(argname) alpha_num(argname, env, sig, pstate, traces) // double - - extern Signature rgb_sig; - extern Signature rgba_4_sig; - extern Signature rgba_2_sig; - extern Signature red_sig; - extern Signature green_sig; - extern Signature blue_sig; - extern Signature mix_sig; - extern Signature hsl_sig; - extern Signature hsla_sig; - extern Signature hue_sig; - extern Signature saturation_sig; - extern Signature lightness_sig; - extern Signature adjust_hue_sig; - extern Signature lighten_sig; - extern Signature darken_sig; - extern Signature saturate_sig; - extern Signature desaturate_sig; - extern Signature grayscale_sig; - extern Signature complement_sig; - extern Signature invert_sig; - extern Signature alpha_sig; - extern Signature opacity_sig; - extern Signature opacify_sig; - extern Signature fade_in_sig; - extern Signature transparentize_sig; - extern Signature fade_out_sig; - extern Signature adjust_color_sig; - extern Signature scale_color_sig; - extern Signature change_color_sig; - extern Signature ie_hex_str_sig; - - BUILT_IN(rgb); - BUILT_IN(rgba_4); - BUILT_IN(rgba_2); - BUILT_IN(red); - BUILT_IN(green); - BUILT_IN(blue); - BUILT_IN(mix); - BUILT_IN(hsl); - BUILT_IN(hsla); - BUILT_IN(hue); - BUILT_IN(saturation); - BUILT_IN(lightness); - BUILT_IN(adjust_hue); - BUILT_IN(lighten); - BUILT_IN(darken); - BUILT_IN(saturate); - BUILT_IN(desaturate); - BUILT_IN(grayscale); - BUILT_IN(complement); - BUILT_IN(invert); - BUILT_IN(alpha); - BUILT_IN(opacify); - BUILT_IN(transparentize); - BUILT_IN(adjust_color); - BUILT_IN(scale_color); - BUILT_IN(change_color); - BUILT_IN(ie_hex_str); + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + Value* rgbFn( + const sass::string& name, + const ValueVector& arguments, + const SourceSpan& pstate, + Logger& logger, + bool strict = false); + + Value* hslFn( + const sass::string& name, + const ValueVector& arguments, + const SourceSpan& pstate, + Logger& logger, + bool strict = false); + + Value* hwbFn( + const sass::string& name, + const ValueVector& arguments, + const SourceSpan& pstate, + Logger& logger, + bool strict = false); + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + namespace Colors { + + // Register all functions at compiler + void registerFunctions(Compiler& ctx); + + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// } diff --git a/src/fn_lists.cpp b/src/fn_lists.cpp index 1d47d5789a..dfaf0e3281 100644 --- a/src/fn_lists.cpp +++ b/src/fn_lists.cpp @@ -1,285 +1,281 @@ -// 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" +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ #include "fn_lists.hpp" +#include "eval.hpp" +#include "compiler.hpp" +#include "exceptions.hpp" +#include "ast_values.hpp" + namespace Sass { namespace Functions { - ///////////////// - // LIST FUNCTIONS - ///////////////// - - Signature keywords_sig = "keywords($args)"; - BUILT_IN(keywords) - { - List_Obj arglist = SASS_MEMORY_COPY(ARG("$args", List)); // copy - Map_Obj result = SASS_MEMORY_NEW(Map, pstate, 1); - for (size_t i = arglist->size(), L = arglist->length(); i < L; ++i) { - ExpressionObj obj = arglist->at(i); - Argument_Obj arg = (Argument*) obj.ptr(); // XXX - sass::string name = sass::string(arg->name()); - name = name.erase(0, 1); // sanitize name (remove dollar sign) - *result << std::make_pair(SASS_MEMORY_NEW(String_Quoted, - pstate, name), - arg->value()); - } - return result.detach(); - } + namespace Lists { + + /*******************************************************************/ - Signature length_sig = "length($list)"; - BUILT_IN(length) - { - if (SelectorList * sl = Cast(env["$list"])) { - return SASS_MEMORY_NEW(Number, pstate, (double) sl->length()); + BUILT_IN_FN(length) + { + return SASS_MEMORY_NEW(Number, + arguments[0]->pstate(), + (double) arguments[0]->lengthAsList()); } - 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)); + + /*******************************************************************/ + + BUILT_IN_FN(nth) + { + Value* list = arguments[0]; + Value* index = arguments[1]; + return list->getValueAt(index, compiler); } - if (v->concrete_type() == Expression::SELECTOR) { - if (CompoundSelector * h = Cast(v)) { - return SASS_MEMORY_NEW(Number, pstate, (double)h->length()); - } else if (SelectorList * ls = Cast(v)) { - return SASS_MEMORY_NEW(Number, pstate, (double)ls->length()); - } else { - return SASS_MEMORY_NEW(Number, pstate, 1); + + /*******************************************************************/ + + BUILT_IN_FN(setNth) + { + Value* input = arguments[0]; + Value* index = arguments[1]; + + size_t idx = input-> + sassIndexToListIndex( + index, compiler, "n"); + + #ifdef SASS_OPTIMIZE_SELF_ASSIGN + if (eval.assigne && eval.assigne->ptr() == input && input->refcount < AssignableRefCount) { + if (List* lst = input->isaList()) { + lst->set(idx, arguments[2]); + return lst; + } } + #endif + List* list = SASS_MEMORY_NEW(List, + input->pstate(), { input->start(), input->stop() }, + input->separator(), input->hasBrackets()); + list->set(idx, arguments[2]); + return list; } - List* list = Cast(env["$list"]); - return SASS_MEMORY_NEW(Number, - pstate, - (double)(list ? list->size() : 1)); - } + /*******************************************************************/ - Signature nth_sig = "nth($list, $n)"; - BUILT_IN(nth) - { - double nr = ARGVAL("$n"); - Map* m = Cast(env["$list"]); - if (SelectorList * sl = Cast(env["$list"])) { - size_t len = m ? m->length() : sl->length(); - bool empty = m ? m->empty() : sl->empty(); - if (empty) error("argument `$list` of `" + sass::string(sig) + "` must not be empty", pstate, traces); - double index = std::floor(nr < 0 ? len + nr : nr - 1); - if (index < 0 || index > len - 1) error("index out of bounds for `" + sass::string(sig) + "`", pstate, traces); - return Cast(Listize::perform(sl->get(static_cast(index)))); - } - List_Obj l = Cast(env["$list"]); - if (nr == 0) error("argument `$n` of `" + sass::string(sig) + "` must be non-zero", pstate, traces); - // if the argument isn't a list, then wrap it in a singleton list - if (!m && !l) { - l = SASS_MEMORY_NEW(List, pstate, 1); - l->append(ARG("$list", Expression)); - } - size_t len = m ? m->length() : l->length(); - bool empty = m ? m->empty() : l->empty(); - if (empty) error("argument `$list` of `" + sass::string(sig) + "` must not be empty", pstate, traces); - double index = std::floor(nr < 0 ? len + nr : nr - 1); - if (index < 0 || index > len - 1) error("index out of bounds for `" + sass::string(sig) + "`", pstate, traces); - - if (m) { - l = SASS_MEMORY_NEW(List, pstate, 2); - l->append(m->keys()[static_cast(index)]); - l->append(m->at(m->keys()[static_cast(index)])); - return l.detach(); - } - else { - ValueObj rv = l->value_at_index(static_cast(index)); - rv->set_delayed(false); - return rv.detach(); - } - } + BUILT_IN_FN(join) + { + Value* list1 = arguments[0]; + Value* list2 = arguments[1]; + String* separatorParam = arguments[2]->assertString(compiler, "separator"); + Value* bracketedParam = arguments[3]; - Signature set_nth_sig = "set-nth($list, $n, $value)"; - BUILT_IN(set_nth) - { - Map_Obj m = Cast(env["$list"]); - List_Obj l = Cast(env["$list"]); - Number_Obj n = ARG("$n", Number); - ExpressionObj v = ARG("$value", Expression); - if (!l) { - l = SASS_MEMORY_NEW(List, pstate, 1); - l->append(ARG("$list", Expression)); - } - if (m) { - l = m->to_list(pstate); - } - if (l->empty()) error("argument `$list` of `" + sass::string(sig) + "` must not be empty", pstate, traces); - double index = std::floor(n->value() < 0 ? l->length() + n->value() : n->value() - 1); - if (index < 0 || index > l->length() - 1) error("index out of bounds for `" + sass::string(sig) + "`", pstate, traces); - List* result = SASS_MEMORY_NEW(List, pstate, l->length(), l->separator(), false, l->is_bracketed()); - for (size_t i = 0, L = l->length(); i < L; ++i) { - result->append(((i == index) ? v : (*l)[i])); - } - return result; - } + SassSeparator separator = SASS_UNDEF; + if (separatorParam->value() == "auto") { + if (list1->separator() != SASS_UNDEF) { + separator = list1->separator(); + } + else if (list2->separator() != SASS_UNDEF) { + separator = list2->separator(); + } + else { + separator = SASS_SPACE; + } + } + else if (separatorParam->value() == "space") { + separator = SASS_SPACE; + } + else if (separatorParam->value() == "comma") { + separator = SASS_COMMA; + } + else if (separatorParam->value() == "slash") { + separator = SASS_DIV; + } + else { + throw Exception::SassScriptException( + "$separator: Must be \"space\", \"comma\", \"slash\", or \"auto\".", + compiler, pstate); + } - Signature index_sig = "index($list, $value)"; - BUILT_IN(index) - { - Map_Obj m = Cast(env["$list"]); - List_Obj l = Cast(env["$list"]); - ExpressionObj v = ARG("$value", Expression); - if (!l) { - l = SASS_MEMORY_NEW(List, pstate, 1); - l->append(ARG("$list", Expression)); - } - if (m) { - l = m->to_list(pstate); - } - for (size_t i = 0, L = l->length(); i < L; ++i) { - if (Operators::eq(l->value_at_index(i), v)) return SASS_MEMORY_NEW(Number, pstate, (double)(i+1)); - } - return SASS_MEMORY_NEW(Null, pstate); - } + bool bracketed = bracketedParam->isTruthy(); + if (String * str = bracketedParam->isaString()) { + if (str->value() == "auto") { + bracketed = list1->hasBrackets(); + } + } - Signature join_sig = "join($list1, $list2, $separator: auto, $bracketed: auto)"; - BUILT_IN(join) - { - Map_Obj m1 = Cast(env["$list1"]); - Map_Obj m2 = Cast(env["$list2"]); - List_Obj l1 = Cast(env["$list1"]); - List_Obj l2 = Cast(env["$list2"]); - String_Constant_Obj sep = ARG("$separator", String_Constant); - enum Sass_Separator sep_val = (l1 ? l1->separator() : SASS_SPACE); - Value* bracketed = ARG("$bracketed", Value); - bool is_bracketed = (l1 ? l1->is_bracketed() : false); - if (!l1) { - l1 = SASS_MEMORY_NEW(List, pstate, 1); - l1->append(ARG("$list1", Expression)); - sep_val = (l2 ? l2->separator() : SASS_SPACE); - is_bracketed = (l2 ? l2->is_bracketed() : false); - } - if (!l2) { - l2 = SASS_MEMORY_NEW(List, pstate, 1); - l2->append(ARG("$list2", Expression)); - } - if (m1) { - l1 = m1->to_list(pstate); - sep_val = SASS_COMMA; - } - if (m2) { - l2 = m2->to_list(pstate); - } - size_t len = l1->length() + l2->length(); - sass::string sep_str = unquote(sep->value()); - if (sep_str == "space") sep_val = SASS_SPACE; - else if (sep_str == "comma") sep_val = SASS_COMMA; - else if (sep_str != "auto") error("argument `$separator` of `" + sass::string(sig) + "` must be `space`, `comma`, or `auto`", pstate, traces); - String_Constant_Obj bracketed_as_str = Cast(bracketed); - bool bracketed_is_auto = bracketed_as_str && unquote(bracketed_as_str->value()) == "auto"; - if (!bracketed_is_auto) { - is_bracketed = !bracketed->is_false(); - } - List_Obj result = SASS_MEMORY_NEW(List, pstate, len, sep_val, false, is_bracketed); - result->concat(l1); - result->concat(l2); - return result.detach(); - } + #ifdef SASS_OPTIMIZE_SELF_ASSIGN + if (eval.assigne && eval.assigne->ptr() == list2 && list2->refcount < AssignableRefCount) { + if (List* lst = list2->isaList()) { + lst->separator(separator); + lst->hasBrackets(bracketed); + lst->elements().insert(lst->end(), + list2->start(), list2->stop()); + return lst; + } + } + #endif - Signature append_sig = "append($list, $val, $separator: auto)"; - BUILT_IN(append) - { - Map_Obj m = Cast(env["$list"]); - List_Obj l = Cast(env["$list"]); - ExpressionObj v = ARG("$val", Expression); - if (SelectorList * sl = Cast(env["$list"])) { - l = Cast(Listize::perform(sl)); + ValueVector values; + values.insert(values.end(), + list1->start(), list1->stop()); + values.insert(values.end(), + list2->start(), list2->stop()); + return SASS_MEMORY_NEW(List, pstate, + std::move(values), separator, bracketed); } - String_Constant_Obj sep = ARG("$separator", String_Constant); - if (!l) { - l = SASS_MEMORY_NEW(List, pstate, 1); - l->append(ARG("$list", Expression)); + + /*******************************************************************/ + + BUILT_IN_FN(append) + { + Value* list = arguments[0]->assertValue(compiler, "list"); + Value* value = arguments[1]->assertValue(compiler, "val"); + String* separatorParam = arguments[2]->assertString(compiler, "separator"); + SassSeparator separator = SASS_UNDEF; + if (separatorParam->value() == "auto") { + separator = list->separator() == SASS_UNDEF + ? SASS_SPACE : list->separator(); + } + else if (separatorParam->value() == "space") { + separator = SASS_SPACE; + } + else if (separatorParam->value() == "comma") { + separator = SASS_COMMA; + } + else if (separatorParam->value() == "slash") { + separator = SASS_DIV; + } + else { + throw Exception::SassScriptException( + "$separator: Must be \"space\", \"comma\", \"slash\", or \"auto\".", + compiler, pstate); + } + + #ifdef SASS_OPTIMIZE_SELF_ASSIGN + if (eval.assigne && eval.assigne->ptr() == list && list->refcount < AssignableRefCount) { + if (List* lst = list->isaList()) { + lst->separator(separator); + lst->append(value); + return lst; + } + } + #endif + + ValueVector values; + values.insert(values.end(), + list->start(), list->stop()); + values.emplace_back(value); + return SASS_MEMORY_NEW(List, + list->pstate(), std::move(values), + separator, list->hasBrackets()); } - if (m) { - l = m->to_list(pstate); + + /*******************************************************************/ + + BUILT_IN_FN(zip) + { + size_t shortest = sass::string::npos; + sass::vector lists; + for (Value* arg : arguments[0]->start()) { + ValueVector inner; + inner.reserve(arg->lengthAsList()); + std::copy(arg->start(), arg->stop(), + std::back_inserter(inner)); + shortest = std::min(shortest, inner.size()); + lists.emplace_back(inner); + } + if (lists.empty()) { + return SASS_MEMORY_NEW(List, + pstate, {}, SASS_COMMA); + } + ValueVector result; + if (!lists.empty()) { + for (size_t i = 0; i < shortest; i++) { + ValueVector inner; + inner.reserve(lists.size()); + for (ValueVector& arg : lists) { + inner.push_back(arg[i]); + } + result.emplace_back(SASS_MEMORY_NEW( + List, pstate, inner, SASS_SPACE)); + } + } + return SASS_MEMORY_NEW( + List, pstate, result, SASS_COMMA); } - List* result = SASS_MEMORY_COPY(l); - sass::string sep_str(unquote(sep->value())); - if (sep_str != "auto") { // check default first - if (sep_str == "space") result->separator(SASS_SPACE); - else if (sep_str == "comma") result->separator(SASS_COMMA); - else error("argument `$separator` of `" + sass::string(sig) + "` must be `space`, `comma`, or `auto`", pstate, traces); + + /*******************************************************************/ + + BUILT_IN_FN(index) + { + Value* value = arguments[1]; + size_t index = arguments[0]->indexOf(value); + if (index == NPOS) { + return SASS_MEMORY_NEW(Null, + arguments[0]->pstate()); + } + return SASS_MEMORY_NEW(Number, + arguments[0]->pstate(), + (double) index + 1); } - if (l->is_arglist()) { - result->append(SASS_MEMORY_NEW(Argument, - v->pstate(), - v, - "", - false, - false)); - - } else { - result->append(v); + + /*******************************************************************/ + + BUILT_IN_FN(separator) + { + return SASS_MEMORY_NEW(String, arguments[0]->pstate(), + std::move(arguments[0]->separator() == SASS_COMMA ? "comma" : + arguments[0]->separator() == SASS_DIV ? "slash" : "space")); } - 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()); + /*******************************************************************/ + + BUILT_IN_FN(isBracketed) + { + return SASS_MEMORY_NEW(Boolean, pstate, + arguments[0]->hasBrackets()); } - List* zippers = SASS_MEMORY_NEW(List, pstate, shortest, SASS_COMMA); - size_t L = arglist->length(); - for (size_t i = 0; i < shortest; ++i) { - List* zipper = SASS_MEMORY_NEW(List, pstate, L); - for (size_t j = 0; j < L; ++j) { - zipper->append(Cast(arglist->value_at_index(j))->at(i)); + + /*******************************************************************/ + + BUILT_IN_FN(slash) + { + + if (arguments[0]->lengthAsList() < 2) { + throw Exception::SassScriptException( + "At least two elements are required.", + compiler, pstate); } - zippers->append(zipper); + + ValueVector inner; + inner.reserve(arguments[0]->lengthAsList()); + std::copy(arguments[0]->start(), arguments[0]->stop(), + std::back_inserter(inner)); + return SASS_MEMORY_NEW(List, pstate, inner, + SASS_DIV, arguments[0]->hasBrackets()); } - 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)); + /*******************************************************************/ + + void registerFunctions(Compiler& ctx) + { + + BuiltInMod& module(ctx.createModule("list")); + + module.addFunction(key_length, ctx.registerBuiltInFunction(key_length, "$list", length)); + module.addFunction(key_nth, ctx.registerBuiltInFunction(key_nth, "$list, $n", nth)); + module.addFunction(key_set_nth, ctx.registerBuiltInFunction(key_set_nth, "$list, $n, $value", setNth)); + module.addFunction(key_join, ctx.registerBuiltInFunction(key_join, "$list1, $list2, $separator: auto, $bracketed : auto", join)); + module.addFunction(key_append, ctx.registerBuiltInFunction(key_append, "$list, $val, $separator: auto", append)); + module.addFunction(key_zip, ctx.registerBuiltInFunction(key_zip, "$lists...", zip)); + module.addFunction(key_index, ctx.registerBuiltInFunction(key_index, "$list, $value", index)); + module.addFunction(key_list_separator, ctx.registerBuiltInFunction(key_list_separator, "$list", separator)); + module.addFunction(key_is_bracketed, ctx.registerBuiltInFunction(key_is_bracketed, "$list", isBracketed)); + module.addFunction(key_slash, ctx.registerBuiltInFunction(key_slash, "$elements...", slash)); + } - return SASS_MEMORY_NEW(String_Quoted, - pstate, - l->separator() == SASS_COMMA ? "comma" : "space"); - } - Signature is_bracketed_sig = "is-bracketed($list)"; - BUILT_IN(is_bracketed) - { - ValueObj value = ARG("$list", Value); - List_Obj list = Cast(value); - return SASS_MEMORY_NEW(Boolean, pstate, list && list->is_bracketed()); - } + /*******************************************************************/ + } } } diff --git a/src/fn_lists.hpp b/src/fn_lists.hpp index 260023aeb7..d8e4622261 100644 --- a/src/fn_lists.hpp +++ b/src/fn_lists.hpp @@ -1,5 +1,12 @@ -#ifndef SASS_FN_LISTS_H -#define SASS_FN_LISTS_H +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#ifndef SASS_FN_LISTS_HPP +#define SASS_FN_LISTS_HPP + +// sass.hpp must go before all system headers +// to get the __EXTENSIONS__ fix on Solaris. +#include "capi_sass.hpp" #include "fn_utils.hpp" @@ -7,25 +14,18 @@ namespace Sass { namespace Functions { - extern Signature length_sig; - extern Signature nth_sig; - extern Signature index_sig; - extern Signature join_sig; - extern Signature append_sig; - extern Signature zip_sig; - extern Signature list_separator_sig; - extern Signature is_bracketed_sig; - extern Signature keywords_sig; - - BUILT_IN(length); - BUILT_IN(nth); - BUILT_IN(index); - BUILT_IN(join); - BUILT_IN(append); - BUILT_IN(zip); - BUILT_IN(list_separator); - BUILT_IN(is_bracketed); - BUILT_IN(keywords); + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + namespace Lists { + + // Register all functions at compiler + void registerFunctions(Compiler& ctx); + + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// } diff --git a/src/fn_maps.cpp b/src/fn_maps.cpp index 0db5acab76..28a3414d15 100644 --- a/src/fn_maps.cpp +++ b/src/fn_maps.cpp @@ -1,94 +1,517 @@ -#include "operators.hpp" -#include "fn_utils.hpp" +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ #include "fn_maps.hpp" +#include "eval.hpp" +#include "compiler.hpp" +#include "exceptions.hpp" +#include "ast_values.hpp" + namespace Sass { namespace Functions { - ///////////////// - // MAP FUNCTIONS - ///////////////// - - Signature map_get_sig = "map-get($map, $key)"; - BUILT_IN(map_get) - { - // leaks for "map-get((), foo)" if not Obj - // investigate why this is (unexpected) - Map_Obj m = ARGM("$map", Map); - ExpressionObj v = ARG("$key", Expression); - try { - ValueObj val = m->at(v); - if (!val) return SASS_MEMORY_NEW(Null, pstate); - val->set_delayed(false); - return val.detach(); - } catch (const std::out_of_range&) { + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + namespace Maps { + + /*******************************************************************/ + + // Merges [map1] and [map2], with values in [map2] taking precedence. + // If both [map1] and [map2] have a map value associated with + // the same key, this recursively merges those maps as well. + Map* deepMergeImpl(Map* map1, Map* map2) + { + + if (map2->empty()) return map1; + if (map1->empty()) return map2; + + // Avoid making a mutable copy of `map2` if it would totally overwrite `map1` + // anyway. + // var mutable = false; + // var result = map2.contents; + // void _ensureMutable() { + // if (mutable) return; + // mutable = true; + // result = Map.of(result); + // } + + MapObj result = SASS_MEMORY_COPY(map1); + + // Because values in `map2` take precedence over `map1`, we just check if any + // entries in `map1` don't have corresponding keys in `map2`, or if they're + // maps that need to be merged in their own right. + // Note: this changes insertion order, bad!? + for (auto kv : map2->elements()) { + Value* key = kv.first; + Value* value = kv.second; + auto it = result->find(key); + if (it != result->end()) { + Map* valueMap = value->isaMap(); + Map* resultMap = it->second->isaMap(); + if (resultMap != nullptr && valueMap != nullptr) { + Map* merged = deepMergeImpl(resultMap, valueMap); + // if (identical(merged, resultMap)) return; + it.value() = merged; + } + else { + // Might got an empty list that could be a map + List* resultList = kv.second->isaList(); + if (resultList && resultList->empty()) { + // it.value() = kv.second; + } + else { + it.value() = kv.second; + } + } + } + else { + result->insert(kv); + } + } + //std::cerr << "Result " << result->inspect() << "\n"; + return result.detach(); + } + + + // Merges [map1] and [map2], with values in [map2] taking precedence. + // If both [map1] and [map2] have a map value associated with + // the same key, this recursively merges those maps as well. + Map* deepMergeImplOptimized(Map* map1, Map* map2) + { + + if (map2->empty()) return map1; + + // Avoid making a mutable copy of `map2` if it would totally overwrite `map1` + // anyway. + // var mutable = false; + // var result = map2.contents; + // void _ensureMutable() { + // if (mutable) return; + // mutable = true; + // result = Map.of(result); + // } + + MapObj result = SASS_MEMORY_COPY(map2); + + // Because values in `map2` take precedence over `map1`, we just check if any + // entries in `map1` don't have corresponding keys in `map2`, or if they're + // maps that need to be merged in their own right. + // Note: this changes insertion order, bad!? + for (auto kv : map1->elements()) { + Value* key = kv.first; + Value* value = kv.second; + auto it = result->find(key); + if (it != result->end()) { + Map* valueMap = value->isaMap(); + Map* resultMap = it->second->isaMap(); + if (resultMap != nullptr && valueMap != nullptr) { + Map* merged = deepMergeImplOptimized(valueMap, resultMap); + // if (identical(merged, resultMap)) return; + it.value() = merged; + } + else { + // Might got an empty list that could be a map + List* resultList = it->second->isaList(); + if (resultList && resultList->empty()) { + it.value() = kv.second; + } + } + } + else { + result->insert(kv); + } + } + + return result.detach(); + } + + /*******************************************************************/ + + BUILT_IN_FN(get) + { + MapObj map = arguments[0]->assertMap(compiler, Strings::map); + Value* key = arguments[1]->assertValue(compiler, Strings::key); + + + auto it = map->find(key); + if (it != map->end()) { + Value* rv = it->second; + + auto cur = arguments[2]->start(); + auto end = arguments[2]->stop(); + if (cur == end) { + return rv; + } + while (cur != end) { + if (auto deep = rv->isaMap()) { + auto dada = deep->find(*cur); + if (dada != deep->end()) { + rv = dada.value(); + } + else { + return SASS_MEMORY_NEW(Null, pstate); + } + } + else { + return SASS_MEMORY_NEW(Null, pstate); + } + ++cur; + } + + return rv; + } return SASS_MEMORY_NEW(Null, pstate); } - catch (...) { throw; } - } - Signature map_has_key_sig = "map-has-key($map, $key)"; - BUILT_IN(map_has_key) - { - Map_Obj m = ARGM("$map", Map); - ExpressionObj v = ARG("$key", Expression); - return SASS_MEMORY_NEW(Boolean, pstate, m->has(v)); - } - Signature map_keys_sig = "map-keys($map)"; - BUILT_IN(map_keys) - { - Map_Obj m = ARGM("$map", Map); - List* result = SASS_MEMORY_NEW(List, pstate, m->length(), SASS_COMMA); - for ( auto key : m->keys()) { - result->append(key); + /*******************************************************************/ + + BUILT_IN_FN(fnMapSetThreeArgs) + { + MapObj map = arguments[0]->assertMap(compiler, Strings::map); + auto copy = SASS_MEMORY_COPY(map); // Can be optimized! + auto it = copy->find(arguments[1]); + if (it == copy->end()) { + copy->insert({ arguments[1], arguments[2] }); + } + else { + it.value() = arguments[2]; + } + return copy; } - return result; - } - Signature map_values_sig = "map-values($map)"; - BUILT_IN(map_values) - { - Map_Obj m = ARGM("$map", Map); - List* result = SASS_MEMORY_NEW(List, pstate, m->length(), SASS_COMMA); - for ( auto key : m->keys()) { - result->append(m->at(key)); + /*******************************************************************/ + + BUILT_IN_FN(fnMapSetTwoArgs) + { + MapObj map = arguments[0]->assertMap(compiler, Strings::map); + MapObj copy = map = SASS_MEMORY_COPY(map); // Can be optimized + auto cur = arguments[1]->start(); + auto end = arguments[1]->stop(); + auto size = arguments[1]->lengthAsList(); + if (size == 0) { + throw Exception::RuntimeException(compiler, + "Expected $args to contain a key."); + } + else if (size == 1) { + throw Exception::RuntimeException(compiler, + "Expected $args to contain a value."); + } + + + while (cur != end) { + auto qwe = *cur; + auto it = map->find(qwe); + if (it != map->end()) { + ValueObj inner = it->second; + if (auto qwe = inner->isaMap()) { + map = qwe; + } + else { + if (cur == end - 2) { + ++cur; + it.value() = *cur; + break; + } + else { + auto newMap = SASS_MEMORY_NEW(Map, + map->pstate(), {}); + it.value() = newMap; + map = newMap; + } + } + } + else { + if (cur == end - 2) { + ++cur; + map->insert({ qwe, *cur }); + break; + } + else { + auto newMap = SASS_MEMORY_NEW(Map, + map->pstate(), {}); + map->insert({ qwe, newMap }); + map = newMap; + } + } + ++cur; + } + + return copy.detach(); } - 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)); + BUILT_IN_FN(merge) + { + MapObj map1 = arguments[0]->assertMap(compiler, Strings::map1); + MapObj map2 = arguments[1]->assertMap(compiler, Strings::map2); + // We assign to ourself, so we can optimize this + // This can shave off a few percent of run-time + #ifdef SASS_OPTIMIZE_SELF_ASSIGN + if (eval.assigne && eval.assigne->ptr() == map1.ptr()) { + if (map1->refcount < AssignableRefCount + 1) { + for (auto kv : map2->elements()) { map1->insertOrSet(kv); } + return map1.detach(); + } + } + #endif + Map* copy = SASS_MEMORY_COPY(map1); + for (auto kv : map2->elements()) { + copy->insertOrSet(kv); } + return copy; } - return result; + + /*******************************************************************/ + + BUILT_IN_FN(merge_many) + { + MapObj map1 = arguments[0]->assertMap(compiler, Strings::map1); + + auto size = arguments[1]->lengthAsList(); + + if (size == 0) { + throw Exception::RuntimeException(compiler, + "Expected $args to contain a key."); + } + else if (size == 1) { + throw Exception::RuntimeException(compiler, + "Expected $args to contain a map."); + } + + auto cur = arguments[1]->start(); + auto end = arguments[1]->stop() - 1; + + MapObj last = (end)->assertMap(compiler, Strings::map2); + MapObj copy = map1 = SASS_MEMORY_COPY(map1); + + while (cur != end) { + + auto it = copy->find(*cur); + if (it != copy->end()) { + if (auto inner = it->second->isaMap()) { + copy = SASS_MEMORY_COPY(inner); + it.value() = copy; + } + else { + Map* empty = SASS_MEMORY_NEW(Map, + it->second->pstate()); + it.value() = empty; + copy = empty; + } + } + else { + Map* empty = SASS_MEMORY_NEW(Map, + last->pstate()); + copy->insert({ *cur, empty }); + copy = empty; + } + + // if (!cur->isaMap()) { + // *cur = SASS_MEMORY_NEW(Map, ) + // } + + ++cur; + } + + for (auto kv : last->elements()) { + copy->insertOrSet(kv); } + return map1.detach(); + } + + /*******************************************************************/ + + // Because the signature below has an explicit `$key` argument, it doesn't + // allow zero keys to be passed. We want to allow that case, so we add an + // explicit overload for it. + BUILT_IN_FN(remove_one) + { + return arguments[0]->assertMap(compiler, Strings::map); + } + + /*******************************************************************/ + + BUILT_IN_FN(remove_many) + { + MapObj map = arguments[0]->assertMap(compiler, Strings::map); + + #ifdef SASS_OPTIMIZE_SELF_ASSIGN + if (eval.assigne && eval.assigne->ptr() == map.ptr() && map->refcount < AssignableRefCount + 1) { + map->erase(arguments[1]); + for (Value* key : arguments[2]->start()) { + map->erase(key); + } + return map.detach(); + } + #endif + + MapObj copy = SASS_MEMORY_COPY(map); + copy->erase(arguments[1]); + for (Value* key : arguments[2]->start()) { + copy->erase(key); + } + return copy.detach(); + } + + /*******************************************************************/ + + BUILT_IN_FN(keys) + { + MapObj map = arguments[0]->assertMap(compiler, Strings::map); + return SASS_MEMORY_NEW(List, pstate, std::move(map->keys()), SASS_COMMA); + } + + /*******************************************************************/ + + BUILT_IN_FN(values) + { + MapObj map = arguments[0]->assertMap(compiler, Strings::map); + return SASS_MEMORY_NEW(List, pstate, std::move(map->values()), SASS_COMMA); + } + + /*******************************************************************/ + + BUILT_IN_FN(hasKey) + { + MapObj map = arguments[0]->assertMap(compiler, Strings::map); + Value* key = arguments[1]->assertValue(compiler, Strings::key); + if (arguments[2]->lengthAsList() == 0) { + return SASS_MEMORY_NEW(Boolean, pstate, map->has(key)); + } + else { + Map* current = map; + auto first = current->find(key); + if (first != current->end()) { + current = first->second->isaMap(); + if (!current) { + return SASS_MEMORY_NEW(Boolean, pstate, false); + } + auto cur = arguments[2]->start(); + auto end = arguments[2]->stop() - 1; + Value* last = *end; + while (cur != end) { + auto inner = current->find(*cur); + if (inner != current->end()) { + if (auto imap = inner->second->isaMap()) { + current = imap; + } + else { + return SASS_MEMORY_NEW(Boolean, pstate, false); + } + } + else { + return SASS_MEMORY_NEW(Boolean, pstate, false); + } + ++cur; + } + + // Still here, check now + auto rv = current->find(last); + return SASS_MEMORY_NEW(Boolean, pstate, + rv != current->end()); + + } + else { + return SASS_MEMORY_NEW(Boolean, pstate, false); + } + + } + return SASS_MEMORY_NEW(Boolean, pstate, false); + } + + /*******************************************************************/ + + BUILT_IN_FN(fnDeepMerge) + { + MapObj map1 = arguments[0]->assertMap(compiler, Strings::map1); + MapObj map2 = arguments[1]->assertMap(compiler, Strings::map2); + MapObj result = deepMergeImpl(map1, map2); + return result.detach(); + } + + /*******************************************************************/ + + BUILT_IN_FN(fnDeepRemove) + { + MapObj map = arguments[0]->assertMap(compiler, Strings::map); + MapObj result = SASS_MEMORY_COPY(map); + Map* level = result; + + auto cur = arguments[2]->start(); + auto end = arguments[2]->stop(); + + if (cur == end) { + level->erase(arguments[1]); + return result.detach(); + } + else { + auto child = level->find(arguments[1]); + if (child == level->end()) return result.detach(); + auto childMap = child->second->isaMap(); + if (childMap == nullptr) return result.detach(); + child.value() = level = SASS_MEMORY_COPY(childMap); + } + + while (cur != end) { + if (cur.isLast()) { + level->erase(*cur); + return result.detach(); + } + else { + // Go further down one key + auto child = level->find(*cur); + if (child == level->end()) return result.detach(); + auto childMap = child->second->isaMap(); + if (childMap == nullptr) return result.detach(); + child.value() = level = SASS_MEMORY_COPY(childMap); + } + ++cur; + } + return result.detach(); + } + + /*******************************************************************/ + + void registerFunctions(Compiler& ctx) + { + BuiltInMod& module(ctx.createModule("map")); + + module.addFunction(key_set, ctx.createBuiltInOverloadFns(key_map_set, { + std::make_pair("$map, $key, $value", fnMapSetThreeArgs), + std::make_pair("$map, $args...", fnMapSetTwoArgs) + })); + + module.addFunction(key_get, ctx.registerBuiltInFunction(key_map_get, "$map, $key, $keys...", get)); + + module.addFunction(key_merge, ctx.registerBuiltInOverloadFns(key_map_merge, { + std::make_pair("$map1, $map2", merge), + std::make_pair("$map1, $args...", merge_many) + })); + + module.addFunction(key_remove, ctx.registerBuiltInOverloadFns(key_map_remove, { + std::make_pair("$map", remove_one), + std::make_pair("$map, $key, $keys...", remove_many) + })); + + module.addFunction(key_keys, ctx.registerBuiltInFunction(key_map_keys, "$map", keys)); + module.addFunction(key_values, ctx.registerBuiltInFunction(key_map_values, "$map", values)); + module.addFunction(key_has_key, ctx.registerBuiltInFunction(key_map_has_key, "$map, $key, $keys...", hasKey)); + + module.addFunction(key_deep_merge, ctx.createBuiltInFunction(key_deep_merge, "$map1, $map2", fnDeepMerge)); + module.addFunction(key_deep_remove, ctx.createBuiltInFunction(key_deep_remove, "$map, $key, $keys...", fnDeepRemove)); + + } + + /*******************************************************************/ + } + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + } -} \ No newline at end of file +} diff --git a/src/fn_maps.hpp b/src/fn_maps.hpp index 9551ec33ec..191bbb84a7 100644 --- a/src/fn_maps.hpp +++ b/src/fn_maps.hpp @@ -1,5 +1,12 @@ -#ifndef SASS_FN_MAPS_H -#define SASS_FN_MAPS_H +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#ifndef SASS_FN_MAPS_HPP +#define SASS_FN_MAPS_HPP + +// sass.hpp must go before all system headers +// to get the __EXTENSIONS__ fix on Solaris. +#include "capi_sass.hpp" #include "fn_utils.hpp" @@ -7,21 +14,18 @@ namespace Sass { namespace Functions { - #define ARGM(argname, argtype) get_arg_m(argname, env, sig, pstate, traces) - - extern Signature map_get_sig; - extern Signature map_merge_sig; - extern Signature map_remove_sig; - extern Signature map_keys_sig; - extern Signature map_values_sig; - extern Signature map_has_key_sig; - - BUILT_IN(map_get); - BUILT_IN(map_merge); - BUILT_IN(map_remove); - BUILT_IN(map_keys); - BUILT_IN(map_values); - BUILT_IN(map_has_key); + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + namespace Maps { + + // Register all functions at compiler + void registerFunctions(Compiler& ctx); + + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// } diff --git a/src/fn_math.cpp b/src/fn_math.cpp new file mode 100644 index 0000000000..fd6525dc68 --- /dev/null +++ b/src/fn_math.cpp @@ -0,0 +1,491 @@ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#include "fn_math.hpp" + +#include +#include "compiler.hpp" +#include "exceptions.hpp" +#include "ast_values.hpp" +#include "calculation.hpp" + +namespace Sass { + + namespace Functions { + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + namespace Math { + + /*******************************************************************/ + + double coerceToRad(Number* number, Logger& compiler, const sass::string& vname) { + Units radiants("rad"); + // if (std::isinf(number->value())) return number->value(); + if (double factor = number->getUnitConversionFactor(radiants)) { + return number->value() * factor; + } + callStackFrame csf(compiler, number->pstate()); + throw Exception::RuntimeException(compiler, "$" + vname + + ": Expected " + number->inspect() + " to be an angle."); + } + + /*******************************************************************/ + + BUILT_IN_FN(round) + { + Number* number = arguments[0]->assertNumber(compiler, "number"); + return SASS_MEMORY_NEW(Number, pstate, + std::round(number->value()), + number->unit()); + } + + /*******************************************************************/ + + BUILT_IN_FN(ceil) + { + Number* number = arguments[0]->assertNumber(compiler, "number"); + return SASS_MEMORY_NEW(Number, pstate, + std::ceil(number->value()), + number->unit()); + } + BUILT_IN_FN(fnClamp) + { + + Number* min = arguments[0]->assertNumber(compiler, "min"); + Number* number = arguments[1]->assertNumber(compiler, "number"); + Number* max = arguments[2]->assertNumber(compiler, "max"); + if (min->hasUnits() == number->hasUnits() && number->hasUnits() == max->hasUnits()) { + if (min->greaterThanOrEquals(max, compiler, pstate)) return min; + if (min->greaterThanOrEquals(number, compiler, pstate)) return min; + if (number->greaterThanOrEquals(max, compiler, pstate)) return max; + return number; + } + + auto arg2 = min->hasUnits() != number->hasUnits() ? number : max; + auto arg2Name = min->hasUnits() != number->hasUnits() ? "$number" : "$max"; + throw Exception::RuntimeException(compiler, + sass::string(arg2Name) + ": " + arg2->inspect() + " and " + + "$min: " + min->inspect() + " have incompatible units " + + "(one has units and the other doesn't)."); + } + + + /*******************************************************************/ + + BUILT_IN_FN(floor) + { + Number* number = arguments[0]->assertNumber(compiler, "number"); + return SASS_MEMORY_NEW(Number, pstate, + std::floor(number->value()), + number->unit()); + } + + /*******************************************************************/ + + BUILT_IN_FN(abs) + { + Number* number = arguments[0]->assertNumber(compiler, "number"); + return SASS_MEMORY_NEW(Number, pstate, + std::abs(number->value()), + number->unit()); + } + + BUILT_IN_FN(fnHypot) + { + sass::vector numbers; + for (Value* value : arguments[0]->start()) { + numbers.push_back(value->assertNumber(compiler, "")); + } + + if (numbers.empty()) { + throw Exception::RuntimeException(compiler, + "At least one argument must be passed."); + } + + auto numeratorUnits = numbers[0]->numerators; + auto denominatorUnits = numbers[0]->denominators; + auto subtotal = 0.0; + for (size_t i = 0; i < numbers.size(); i++) { + auto& number = numbers[i]; + if (number->hasUnits() == numbers[0]->hasUnits()) { + if (double factor = number->getUnitConversionFactor(numbers[0])) { + subtotal += std::pow(number->value() * factor, 2.0); + } + else { + throw Exception::UnitMismatch(compiler, numbers[0], number); + } + } + else { + sass::sstream strm; strm << (i + 1); + throw Exception::RuntimeException(compiler, + "$numbers[" + strm.str() + "]: " + number->inspect() + " and " + + "$numbers[1]: " + numbers[0]->inspect() + " have incompatible units " + + "(one has units and the other doesn't)."); + } + } + + return SASS_MEMORY_NEW(Number, pstate, + std::sqrt(subtotal), numbers[0]); + + } + + BUILT_IN_FN(fnLog) + { + auto number = arguments[0]->assertNumber(compiler, Strings::number); + if (number->hasUnits()) { + throw Exception::RuntimeException(compiler, "$number: " + "Expected " + number->inspect() + " to have no units."); + } + + if (arguments[1]->isNull()) { + return SASS_MEMORY_NEW(Number, + pstate, std::log(number->value())); + } + + auto base = arguments[1]->assertNumber(compiler, "base"); + if (base->hasUnits()) { + throw Exception::RuntimeException(compiler, "$base: " + "Expected " + base->inspect() + " to have no units."); + } + + return SASS_MEMORY_NEW(Number, pstate, + std::log(number->value()) / std::log(base->value())); + } + + BUILT_IN_FN(fnDiv) + { + Number* number1 = arguments[0]->isaNumber(); + Number* number2 = arguments[1]->isaNumber(); + + if (number1 == nullptr || number2 == nullptr) { + // callStackFrame csf(compiler, arguments[0]->pstate()); + compiler.printWarning("math.div() will only support number arguments in a future release.\n" + "Use list.slash() instead for a slash separator.", pstate, Logger::WARN_MATH_DIV); + // compiler.addWarning(); + } + + return arguments[0]->dividedBy(arguments[1], compiler, pstate); + + } + + BUILT_IN_FN(fnPow) + { + + auto base = arguments[0]->assertNumber(compiler, "base"); + auto exponent = arguments[1]->assertNumber(compiler, "exponent"); + if (base->hasUnits()) { + throw Exception::RuntimeException(compiler, "$base: " + "Expected " + base->inspect() + " to have no units."); + } + if (exponent->hasUnits()) { + throw Exception::RuntimeException(compiler, "$exponent: " + "Expected " + exponent->inspect() + " to have no units."); + } + + // Exponentiating certain real numbers leads to special behaviors. Ensure that + // these behaviors are consistent for numbers within the precision limit. + auto baseValue = base->value(); + auto expValue = exponent->value(); + return SASS_MEMORY_NEW(Number, pstate, + std::pow(baseValue, expValue)); + + + } + + BUILT_IN_FN(fnSqrt) + { + auto number = arguments[0]->assertNumber(compiler, "number"); + if (number->hasUnits()) { + throw Exception::RuntimeException(compiler, "$number: " + "Expected " + number->inspect() + " to have no units."); + } + return SASS_MEMORY_NEW(Number, pstate, std::sqrt( + number->value())); + } + + + /*******************************************************************/ + + BUILT_IN_FN(max) + { + ValueVector foobar; + for (Value* value : arguments[0]->start()) { + foobar.push_back(value); + } + return Calculation32::calc_max(compiler, pstate, foobar); + Number* max = nullptr; + for (Value* value : arguments[0]->start()) { + Number* number = value->assertNumber(compiler, ""); + if (max == nullptr || max->lessThan(number, compiler, pstate)) { + max = number; + } + } + if (max != nullptr) return max; + // Report invalid arguments error + throw Exception::SassScriptException( + "At least one argument must be passed.", + compiler, pstate); + } + + /*******************************************************************/ + + BUILT_IN_FN(min) + { + ValueVector foobar; + for (Value* value : arguments[0]->start()) { + foobar.push_back(value); + } + return Calculation32::calc_min(compiler, pstate, foobar); + + Number* min = nullptr; + for (Value* value : arguments[0]->start()) { + Number* number = value->assertNumber(compiler, ""); + if (min == nullptr || min->greaterThan(number, compiler, pstate)) { + min = number; + } + } + if (min != nullptr) return min; + // Report invalid arguments error + throw Exception::SassScriptException( + "At least one argument must be passed.", + compiler, pstate); + } + + /*******************************************************************/ + + BUILT_IN_FN(random) + { + if (arguments[0]->isNull()) { + return SASS_MEMORY_NEW(Number, pstate, + getRandomDouble(0, 1)); + } + Number* nr = arguments[0]->assertNumber(compiler, "limit"); + long limit = nr->assertInt(compiler, "limit"); + if (limit >= 1) { + return SASS_MEMORY_NEW(Number, pstate, + (long) getRandomDouble(1, double(limit) + 1)); + } + // Report invalid arguments error + sass::sstream strm; + strm << "$limit: Must be greater than 0, was " << limit << "."; + throw Exception::SassScriptException(strm.str(), compiler, pstate); + } + + /*******************************************************************/ + + BUILT_IN_FN(unit) + { + Number* number = arguments[0]->assertNumber(compiler, "number"); + sass::string copy(number->unit()); + return SASS_MEMORY_NEW(String, pstate, std::move(copy), true); + } + + /*******************************************************************/ + + BUILT_IN_FN(isUnitless) + { + Number* number = arguments[0]->assertNumber(compiler, "number"); + return SASS_MEMORY_NEW(Boolean, pstate, !number->hasUnits()); + } + + /*******************************************************************/ + + BUILT_IN_FN(percentage) + { + Number* number = arguments[0]->assertNumber(compiler, "number"); + number->assertUnitless(compiler, "number"); + return SASS_MEMORY_NEW(Number, pstate, + number->value() * 100, "%"); + } + + /*******************************************************************/ + + BUILT_IN_FN(compatible) + { + Number* n1 = arguments[0]->assertNumber(compiler, "number1"); + Number* n2 = arguments[1]->assertNumber(compiler, "number2"); + if (n1->isUnitless() || n2->isUnitless()) { + return SASS_MEMORY_NEW(Boolean, pstate, true); + } + // normalize into main units + n1->normalize(); n2->normalize(); + Units& lhs_unit = *n1, & rhs_unit = *n2; + bool is_comparable = (lhs_unit == rhs_unit); + return SASS_MEMORY_NEW(Boolean, pstate, is_comparable); + } + + /*******************************************************************/ + + BUILT_IN_FN(fnCos) + { + // if (arguments.size() > 1) throw Exception::TooManyArguments(compiler, arguments.size(), 1); + // else if (arguments.size() < 1) throw Exception::MissingArgument(compiler, str_angle); + // AstNode* simplified = arguments[0]->simplify(compiler); + // auto* number = dynamic_cast(simplified); + // if (number == nullptr) return SASS_MEMORY_NEW( + // Calculation, pstate, str_cos, { simplified }); + // double factor = number->factorToUnits(unit_rad); + // if (factor == 0.0) throw Exception::NoAngleArgument(compiler, number, str_angle); + // auto result = std::cos(number->value() * factor); + // return SASS_MEMORY_NEW(Number, number->pstate(), result); + + + Number* number = arguments[0]->assertNumber(compiler, Strings::number); + return SASS_MEMORY_NEW(Number, pstate, + std::cos(coerceToRad(number, compiler, Strings::number))); + } + + /*******************************************************************/ + + BUILT_IN_FN(fnSin) + { + Number* number = arguments[0]->assertNumber(compiler, Strings::number); + return SASS_MEMORY_NEW(Number, pstate, + std::sin(coerceToRad(number, compiler, Strings::number))); + } + + /*******************************************************************/ + + BUILT_IN_FN(fnTan) + { + Number* number = arguments[0]->assertNumber(compiler, Strings::number); + // double asymptoteInterval = 0.5 * PI; double tanPeriod = 2.0 * PI; + return SASS_MEMORY_NEW(Number, pstate, + std::tan(coerceToRad(number, compiler, Strings::number))); + } + + /*******************************************************************/ + + BUILT_IN_FN(fnACos) + { + auto number = arguments[0]->assertNumber(compiler, Strings::number); + if (number->hasUnits()) { + throw Exception::RuntimeException(compiler, "$number: " + "Expected " + number->inspect() + " to have no units."); + } + return SASS_MEMORY_NEW(Number, pstate, + std::acos(number->value()) * 180 / PI, "deg"); + } + + /*******************************************************************/ + + BUILT_IN_FN(fnASin) + { + auto number = arguments[0]->assertNumber(compiler, Strings::number); + if (number->hasUnits()) { + throw Exception::RuntimeException(compiler, "$number: " + "Expected " + number->inspect() + " to have no units."); + } + return SASS_MEMORY_NEW(Number, pstate, + std::asin(number->value()) * 180 / PI, "deg"); + } + + /*******************************************************************/ + + BUILT_IN_FN(fnATan) + { + auto number = arguments[0]->assertNumber(compiler, Strings::number); + if (number->hasUnits()) { + throw Exception::RuntimeException(compiler, "$number: " + "Expected " + number->inspect() + " to have no units."); + } + return SASS_MEMORY_NEW(Number, pstate, + std::atan(number->value()) * 180 / PI, "deg"); + } + + /*******************************************************************/ + + BUILT_IN_FN(fnATan2) + { + auto y = arguments[0]->assertNumber(compiler, "y"); + auto x = arguments[1]->assertNumber(compiler, "x"); + if (y->hasUnits() != x->hasUnits()) { + throw Exception::RuntimeException(compiler, + "$x: " + x->inspect() + " and $y: " + + y->inspect() + " have incompatible units " + + "(one has units and the other doesn't)."); + } + + if (double factor = x->getUnitConversionFactor(y)) { + double result = std::atan2(y->value(), x->value() * factor) * 180 / PI; + return SASS_MEMORY_NEW(Number, pstate, result, "deg"); + } + + throw Exception::UnitMismatch(compiler, y, x); + } + + /*******************************************************************/ + + void registerFunctions(Compiler& ctx) + { + + BuiltInMod& module(ctx.createModule("math")); + + module.addVariable(key_e, ctx.createBuiltInVariable(key_e, + SASS_MEMORY_NEW(Number, SourceSpan::internal("[sass:math]"), + 2.71828182845904523536028747135266249775724709369995))); + module.addVariable(key_pi, ctx.createBuiltInVariable(key_pi, + SASS_MEMORY_NEW(Number, SourceSpan::internal("[sass:math]"), + 3.14159265358979323846264338327950288419716939937510))); + module.addVariable(key_tau, ctx.createBuiltInVariable(key_tau, + SASS_MEMORY_NEW(Number, SourceSpan::internal("[sass:math]"), + 3.14159265358979323846264338327950288419716939937510 * 2.0))); + + module.addVariable(key_epsilon, ctx.createBuiltInVariable(key_epsilon, + SASS_MEMORY_NEW(Number, SourceSpan::internal("[sass:math]"), + std::numeric_limits().epsilon()))); + module.addVariable(key_min_number, ctx.createBuiltInVariable(key_min_number, + SASS_MEMORY_NEW(Number, SourceSpan::internal("[sass:math]"), + std::numeric_limits().min()))); + module.addVariable(key_max_number, ctx.createBuiltInVariable(key_max_number, + SASS_MEMORY_NEW(Number, SourceSpan::internal("[sass:math]"), + std::numeric_limits().max()))); + + module.addVariable(key_min_safe_integer, ctx.createBuiltInVariable(key_min_safe_integer, + SASS_MEMORY_NEW(Number, SourceSpan::internal("[sass:math]"), -9007199254740991))); + module.addVariable(key_max_safe_integer, ctx.createBuiltInVariable(key_max_safe_integer, + SASS_MEMORY_NEW(Number, SourceSpan::internal("[sass:math]"), 9007199254740991))); + + module.addFunction(key_ceil, ctx.registerBuiltInFunction(key_ceil, "$number", ceil)); + module.addFunction(key_clamp, ctx.createBuiltInFunction(key_clamp, "$min, $number, $max", fnClamp)); + module.addFunction(key_floor, ctx.registerBuiltInFunction(key_floor, "$number", floor)); + + // Some functions are marked internal (for what exactly?) + module.addFunction(key_max, ctx.registerInternalFunction(key_max, "$numbers...", max)); + module.addFunction(key_min, ctx.registerInternalFunction(key_min, "$numbers...", min)); + module.addFunction(key_round, ctx.registerInternalFunction(key_round, "$number", round)); + module.addFunction(key_abs, ctx.registerInternalFunction(key_abs, "$number", abs)); + + module.addFunction(key_hypot, ctx.createBuiltInFunction(key_hypot, "$number...", fnHypot)); + + module.addFunction(key_log, ctx.createBuiltInFunction(key_log, "$number, $base: null", fnLog)); + module.addFunction(key_pow, ctx.createBuiltInFunction(key_pow, "$base, $exponent", fnPow)); + module.addFunction(key_div, ctx.createBuiltInFunction(key_div, "$number1, $number2", fnDiv)); + module.addFunction(key_sqrt, ctx.createBuiltInFunction(key_sqrt, "$number", fnSqrt)); + module.addFunction(key_cos, ctx.createBuiltInFunction(key_cos, "$number", fnCos)); + module.addFunction(key_sin, ctx.createBuiltInFunction(key_sin, "$number", fnSin)); + module.addFunction(key_tan, ctx.createBuiltInFunction(key_tan, "$number", fnTan)); + module.addFunction(key_acos, ctx.createBuiltInFunction(key_acos, "$number", fnACos)); + module.addFunction(key_asin, ctx.createBuiltInFunction(key_asin, "$number", fnASin)); + module.addFunction(key_atan, ctx.createBuiltInFunction(key_atan, "$number", fnATan)); + module.addFunction(key_atan2, ctx.createBuiltInFunction(key_atan2, "$y, $x", fnATan2)); + module.addFunction(key_random, ctx.registerBuiltInFunction(key_random, "$limit: null", random)); + module.addFunction(key_unit, ctx.registerBuiltInFunction(key_unit, "$number", unit)); + module.addFunction(key_percentage, ctx.registerBuiltInFunction(key_percentage, "$number", percentage)); + module.addFunction(key_is_unitless, ctx.registerBuiltInFunction(key_unitless, "$number", isUnitless)); + module.addFunction(key_compatible, ctx.registerBuiltInFunction(key_comparable, "$number1, $number2", compatible)); + + } + + /*******************************************************************/ + + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + } + +} diff --git a/src/fn_math.hpp b/src/fn_math.hpp new file mode 100644 index 0000000000..eb9920e3bd --- /dev/null +++ b/src/fn_math.hpp @@ -0,0 +1,34 @@ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#ifndef SASS_FN_NUMBERS_HPP +#define SASS_FN_NUMBERS_HPP + +// sass.hpp must go before all system headers +// to get the __EXTENSIONS__ fix on Solaris. +#include "capi_sass.hpp" + +#include "fn_utils.hpp" + +namespace Sass { + + namespace Functions { + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + namespace Math { + + // Register all functions at compiler + void registerFunctions(Compiler& ctx); + + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + } + +} + +#endif diff --git a/src/fn_meta.cpp b/src/fn_meta.cpp new file mode 100644 index 0000000000..3d74902198 --- /dev/null +++ b/src/fn_meta.cpp @@ -0,0 +1,907 @@ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#include "fn_meta.hpp" + +#include "eval.hpp" +#include "compiler.hpp" +#include "exceptions.hpp" +#include "ast_values.hpp" +#include "ast_callables.hpp" +#include "ast_expressions.hpp" +#include "string_utils.hpp" + +#include "debugger.hpp" + +namespace Sass { + + namespace Functions { + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + namespace Meta { + + /*******************************************************************/ + + BUILT_IN_FN(typeOf) + { + sass::string copy(arguments[0]->type()); + return SASS_MEMORY_NEW(String, + pstate, std::move(copy)); + } + + /*******************************************************************/ + + BUILT_IN_FN(inspect) + { + if (arguments[0] == nullptr) { + return SASS_MEMORY_NEW( + String, pstate, "null"); + } + return SASS_MEMORY_NEW(String, + pstate, arguments[0]->inspect()); + } + + /*******************************************************************/ + + BUILT_IN_FN(fnIf) + { + // Always evaluates both sides! + return arguments[0]->isTruthy() ? + arguments[1] : arguments[0]; + } + + /*******************************************************************/ + + BUILT_IN_FN(fnCalcName) + { + auto calculation = arguments[0]->assertCalculation(compiler, Sass::Strings::calc); + return SASS_MEMORY_NEW(String, calculation->pstate(), calculation->name().c_str(), true); + } + + /*******************************************************************/ + + BUILT_IN_FN(fnCalcArgs) + { + auto calculation = arguments[0]->assertCalculation(compiler, Sass::Strings::calc); + const sass::vector& args(calculation->arguments()); + ValueVector values; // args.size() + + for (auto arg : args) { + if (auto* value = arg->isaValue()) { + if (auto* calcop = value->isaCalcOperation()) { + values.push_back(SASS_MEMORY_NEW(String, + value->pstate(), value->toString(), false)); + } + else { + values.push_back(value); + } + } + else { + values.push_back(SASS_MEMORY_NEW(String, + arg->pstate(), arg->toString(), false)); + } + } + + //std::transform(args.begin(), args.end(), + // values.begin(), [&](AstNodeObj& arg) { + // if (auto value = dynamic_cast(arg.ptr())) return value; + // //return (Value*)SASS_MEMORY_NEW(String, arg->pstate(), arg->toString(), false); + // //return nullptr; + // }); + return SASS_MEMORY_NEW(List, calculation->pstate(), std::move(values)); + } + + /*******************************************************************/ + + BUILT_IN_FN(keywords) + { + ArgumentList* argumentList = arguments[0]->assertArgumentList(compiler, Sass::Strings::args); + const ValueFlatMap& keywords = argumentList->keywords(); + MapObj map = SASS_MEMORY_NEW(Map, arguments[0]->pstate()); + for (auto kv : keywords) { + sass::string key = kv.first.norm(); // .substr(1); + // Util::ascii_normalize_underscore(key); + // Wrap string key into a sass value + map->insert(SASS_MEMORY_NEW(String, + kv.second->pstate(), std::move(key) + ), kv.second); + } + return map.detach(); + } + + /*******************************************************************/ + + BUILT_IN_FN(featureExists) + { + String* feature = arguments[0]->assertString(compiler, "feature"); + static const auto* const features = + new std::unordered_set{ + "global-variable-shadowing", + "extend-selector-pseudoclass", + "units-level-3", + "at-error", + "custom-property" + }; + sass::string name(feature->value()); + return SASS_MEMORY_NEW(Boolean, + pstate, features->count(name) == 1); + } + + /*******************************************************************/ + + BUILT_IN_FN(globalVariableExists) + { + String* variable = arguments[0]->assertString(compiler, Sass::Strings::name); + String* plugin = arguments[1]->assertStringOrNull(compiler, Sass::Strings::module); + auto parent = compiler.getCurrentModule(); + if (plugin != nullptr) { + auto pp = parent->module->moduse.find(plugin->value()); + if (pp != parent->module->moduse.end()) { + EnvRefs* module = pp->second.first; + auto it = module->varIdxs.find(variable->value()); + return SASS_MEMORY_NEW(Boolean, pstate, + it != module->varIdxs.end()); + } + else { + throw Exception::RuntimeException(compiler, + "There is no module with the namespace \"" + plugin->value() + "\"."); + } + return SASS_MEMORY_NEW(Boolean, pstate, false); + } + bool hasVar = false; + for (auto global : parent->forwards) { + if (global->varIdxs.count(variable->value()) != 0) { + if (hasVar) { + throw Exception::RuntimeException(compiler, + "This variable is available from multiple global modules."); + } + hasVar = true; + } + } + if (hasVar) return SASS_MEMORY_NEW(Boolean, pstate, true); + EnvRef vidx = compiler.varRoot.findVarIdx(variable->value(), "", true); + if (!vidx.isValid()) return SASS_MEMORY_NEW(Boolean, pstate, false); + auto& var = compiler.varRoot.getVariable(vidx); + return SASS_MEMORY_NEW(Boolean, pstate, !var.isNull()); + + } + + /*******************************************************************/ + + BUILT_IN_FN(variableExists) + { + String* variable = arguments[0]->assertString(compiler, Sass::Strings::name); + EnvRef vidx = compiler.varRoot.findVarIdx(variable->value(), ""); + + bool hasVar = false; + auto parent = compiler.getCurrentModule(); + for (auto global : parent->forwards) { + if (global->varIdxs.count(variable->value()) != 0) { + if (hasVar) { + throw Exception::RuntimeException(compiler, + "This variable is available from multiple global modules."); + } + hasVar = true; + } + } + if (hasVar) return SASS_MEMORY_NEW(Boolean, pstate, true); + if (!vidx.isValid()) return SASS_MEMORY_NEW(Boolean, pstate, false); + auto& var = compiler.varRoot.getVariable(vidx); + return SASS_MEMORY_NEW(Boolean, pstate, !var.isNull()); + } + + /*******************************************************************/ + + BUILT_IN_FN(functionExists) + { + String* variable = arguments[0]->assertString(compiler, Sass::Strings::name); + String* plugin = arguments[1]->assertStringOrNull(compiler, Sass::Strings::module); + auto parent = compiler.getCurrentModule(); + if (plugin != nullptr) { + auto pp = parent->module->moduse.find(plugin->value()); + if (pp != parent->module->moduse.end()) { + EnvRefs* module = pp->second.first; + auto it = module->fnIdxs.find(variable->value()); + return SASS_MEMORY_NEW(Boolean, pstate, + it != module->fnIdxs.end()); + } + else { + throw Exception::RuntimeException(compiler, + "There is no module with the namespace \"" + plugin->value() + "\"."); + } + return SASS_MEMORY_NEW(Boolean, pstate, false); + } + bool hasFn = false; + for (auto global : parent->forwards) { + if (global->fnIdxs.count(variable->value()) != 0) { + if (hasFn) { + throw Exception::RuntimeException(compiler, + "This function is available from multiple global modules."); + } + hasFn = true; + } + } + if (hasFn) return SASS_MEMORY_NEW(Boolean, pstate, true); + EnvRef fidx = compiler.varRoot.findFnIdx(variable->value(), ""); + return SASS_MEMORY_NEW(Boolean, pstate, fidx.isValid()); + } + + /*******************************************************************/ + + BUILT_IN_FN(mixinExists) + { + String* variable = arguments[0]->assertString(compiler, Sass::Strings::name); + String* plugin = arguments[1]->assertStringOrNull(compiler, Sass::Strings::module); + + auto parent = compiler.getCurrentModule(); + if (plugin != nullptr) { + auto pp = parent->module->moduse.find(plugin->value()); + if (pp != parent->module->moduse.end()) { + EnvRefs* module = pp->second.first; + auto it = module->mixIdxs.find(variable->value()); + return SASS_MEMORY_NEW(Boolean, pstate, + it != module->mixIdxs.end()); + } + else { + throw Exception::RuntimeException(compiler, + "There is no module with the namespace \"" + plugin->value() + "\"."); + } + return SASS_MEMORY_NEW(Boolean, pstate, false); + } + bool hasFn = false; + for (auto global : parent->forwards) { + if (global->mixIdxs.count(variable->value()) != 0) { + if (hasFn) { + throw Exception::RuntimeException(compiler, + "This function is available from multiple global modules."); + } + hasFn = true; + } + } + if (hasFn) return SASS_MEMORY_NEW(Boolean, pstate, true); + + auto midx = compiler.varRoot.findMixIdx(variable->value(), ""); + return SASS_MEMORY_NEW(Boolean, pstate, midx.isValid()); + } + + + /*******************************************************************/ + + BUILT_IN_FN(fnApply) + { + // auto mixin = ; + Callable* callable = arguments[0]->assertMixin(compiler, Sass::Strings::mixin)->callable(); + auto arglist = arguments[1]->assertArgumentList(compiler, Sass::Strings::amount); + + if (auto mixin = callable->isaUserDefinedCallable()) { + + // An include expression must reference a mixin rule + MixinRule* rule = mixin->declaration()->isaMixinRule(); + // Sanity assertion + if (rule == nullptr) { + throw Exception::RuntimeException(eval.traces, + "Include doesn't reference a mixin!"); + } + // Create new mixin for content block + // Prepares the content block to be called later + // Content blocks of includes are like mixins themselves + // UserDefinedCallableObj cmixin; + + // cmixin = SASS_MEMORY_NEW(UserDefinedCallable, + // pstate, mixin->name(), + // mixin->declaration(), + // mixin->content()); + + // debug_ast(eval.content, "content: "); + + CallableDeclaration* ctblk = nullptr; + if (eval.content) ctblk = eval.content->declaration(); + CallableArguments* args = SASS_MEMORY_NEW(CallableArguments, pstate, + {}, {}, SASS_MEMORY_NEW(ValueExpression, callable->pstate(), arglist)); + eval.applyMixin(pstate, mixin->name(), mixin, ctblk, args); + return SASS_MEMORY_NEW(Boolean, pstate, false); + /* + auto oldm = eval.inMixin; + eval.inMixin = true; + auto oldc = eval.content; + + eval.content = cmixin; + + // Return value can be ignored, but memory must still be collected + auto rv = eval._runUserDefinedCallable(args, mixin, pstate); + + eval.inMixin = oldm; + eval.content = oldc; + + std::cerr << "Is user defined callable\n"; + return SASS_MEMORY_NEW(Boolean, pstate, false); + */ + + } + else if (auto bc = callable->isaBuiltInCallable()) { + + // An include expression must reference a mixin rule + // MixinRule* rule = bc->isaMixinRule(); + // Sanity assertion + // if (rule == nullptr) { + // throw Exception::RuntimeException(eval.traces, + // "Include doesn't reference a mixin!"); + // } + + CallableDeclaration* ctblk = nullptr; + if (eval.content) ctblk = eval.content->declaration(); + CallableArguments* args = SASS_MEMORY_NEW(CallableArguments, pstate, + {}, {}, SASS_MEMORY_NEW(ValueExpression, callable->pstate(), arglist)); + eval.applyMixin(pstate, bc->name(), bc, ctblk, args); + return SASS_MEMORY_NEW(Boolean, pstate, false); + + std::cerr << "Is built in callable " << bc << "\n"; + } + + +// CallableSignature* sig = SASS_MEMORY_NEW(CallableSignature, pstate, sass::vector()); +// // CallableDeclaration* ctblk = eval.content->declaration(); +// CallableDeclaration* ctblk = nullptr; +// Expression* rest = SASS_MEMORY_NEW(ValueExpression, pstate, arglist); +// CallableArgumentsObj args = SASS_MEMORY_NEW(CallableArguments, pstate, {}, {}, rest); +// +// + // eval.applyMixin(pstate, key_apply, callable, eval.content, args); + + return SASS_MEMORY_NEW(Boolean, pstate, false); + } + + /*******************************************************************/ + + BUILT_IN_FN(fnGetMixin) + { + String* name = arguments[0]->assertString(compiler, Sass::Strings::name); + String* plugin = arguments[1]->assertStringOrNull(compiler, Sass::Strings::module); + + auto parent = compiler.getCurrentModule(); + if (plugin != nullptr) { + auto pp = parent->module->moduse.find(plugin->value()); + if (pp != parent->module->moduse.end()) { + EnvRefs* module = pp->second.first; + auto it = module->mixIdxs.find(name->value()); + if (it != module->mixIdxs.end()) { + return SASS_MEMORY_NEW(Mixin, pstate, + compiler.varRoot.getMixin(it->second)); + } + } + else { + throw Exception::RuntimeException(compiler, + "There is no module with the namespace \"" + plugin->value() + "\"."); + } + return SASS_MEMORY_NEW(Boolean, pstate, false); + } + bool hasFn = false; + for (auto global : parent->forwards) { + if (global->mixIdxs.count(name->value()) != 0) { + if (hasFn) { + throw Exception::RuntimeException(compiler, + "This function is available from multiple global modules."); + } + hasFn = true; + } + } + + auto midx = compiler.varRoot.findMixIdx(name->value(), ""); + if (midx.isValid()) { + auto callable = compiler.varRoot.getMixin(midx); + return SASS_MEMORY_NEW(Mixin, pstate, callable); + } + else { + throw Exception::RuntimeException(compiler, + "Mixin not found: " + name->value()); + } + + // auto name = arguments[0]->assertString(compiler, "name"); + // //auto modul = arguments[1]->realNull ? .assertString("module"); + // + // envi + // + // // Always evaluates both sides! + // return arguments[0]->isTruthy() ? + // arguments[1] : arguments[0]; + } + + /*******************************************************************/ + + BUILT_IN_FN(fnAcceptsContent) + { + Mixin* mixin = arguments[0]->assertMixin(compiler, Sass::Strings::mixin); + if (auto callable = mixin->callable()) + { + if (BuiltInCallable* builtin = callable->isaBuiltInCallable()) { + return SASS_MEMORY_NEW(Boolean, pstate, builtin->acceptsContent()); + } + else if (ExternalCallable* mixfn = callable->isaExternalCallable()) { + if (/* CallableSignature* decl = */ mixfn->declaration()) { + return SASS_MEMORY_NEW(Boolean, pstate, false); + } + } + else if (UserDefinedCallable* mixfn = callable->isaUserDefinedCallable()) { + if (CallableDeclaration* decl = mixfn->declaration()) { + return SASS_MEMORY_NEW(Boolean, pstate, decl->hasContent()); + } + } + else { + throw Exception::RuntimeException(compiler, + "Unknown callable type " + mixin->type() + "."); + } + } + else { + throw Exception::RuntimeException(compiler, + "Mixin has no callable associated."); + } + throw Exception::RuntimeException(compiler, + "Mixin33 has no callable associated."); + } + + /*******************************************************************/ + + BUILT_IN_FN(contentExists) + { + if (!eval.isInMixin()) { + throw Exception::RuntimeException(compiler, + "content-exists() may only be called within a mixin."); + } + return SASS_MEMORY_NEW(Boolean, pstate, + eval.hasContentBlock()); + } + + /*******************************************************************/ + + BUILT_IN_FN(moduleVariables) + { + String* ns = arguments[0]->assertStringOrNull(compiler, Sass::Strings::module); + MapObj list = SASS_MEMORY_NEW(Map, pstate); + auto module = compiler.getCurrentModule(); + auto it = module->module->moduse.find(ns->value()); + if (it != module->module->moduse.end()) { + EnvRefs* refs = it->second.first; + Module* root = it->second.second; + if (root && !root->isCompiled) { + throw Exception::RuntimeException(compiler, "There is " + "no module with namespace \"" + ns->value() + "\"."); + } + for (auto entry : refs->varIdxs) { + auto name = SASS_MEMORY_NEW(String, pstate, + sass::string(entry.first.norm()), true); + EnvRef vidx(refs, entry.second); + list->insert({ name, compiler. + varRoot.getVariable(vidx) }); + } + if (root) + for (auto entry : root->mergedFwdVar) { + auto name = SASS_MEMORY_NEW(String, pstate, + sass::string(entry.first.norm()), true); + EnvRef vidx(entry.second); + list->insert({ name, compiler. + varRoot.getVariable(vidx) }); + } + } + else { + throw Exception::RuntimeException(compiler, "There is " + "no module with namespace \"" + ns->value() + "\"."); + } + return list.detach(); + } + + /*******************************************************************/ + + BUILT_IN_FN(fnModuleMixins) + { + String* ns = arguments[0]->assertString(compiler, Sass::Strings::module); + MapObj list = SASS_MEMORY_NEW(Map, pstate); + auto module = compiler.getCurrentModule(); + auto it = module->module->moduse.find(ns->value()); + if (it != module->module->moduse.end()) { + EnvRefs* refs = it->second.first; + Module* root = it->second.second; + if (root && !root->isCompiled) { + throw Exception::RuntimeException(compiler, "There is " + "no module with namespace \"" + ns->value() + "\"."); + } + for (auto entry : refs->mixIdxs) { + auto name = SASS_MEMORY_NEW(String, pstate, + sass::string(entry.first.norm()), true); + EnvRef fidx(refs, entry.second); + auto callable = compiler.varRoot.getMixin(fidx); + auto fn = SASS_MEMORY_NEW(Mixin, pstate, callable); + list->insert({ name, fn }); + } + if (root) + for (auto entry : root->mergedFwdMix) { + auto name = SASS_MEMORY_NEW(String, pstate, + sass::string(entry.first.norm()), true); + EnvRef fidx(entry.second); + auto callable = compiler.varRoot.getMixin(fidx); + auto fn = SASS_MEMORY_NEW(Mixin, pstate, callable); + list->insert({ name, fn }); + } + } + else { + throw Exception::RuntimeException(compiler, "There is " + "no module with namespace \"" + ns->value() + "\"."); + } + return list.detach(); + } + + BUILT_IN_FN(moduleFunctions) + { + String* ns = arguments[0]->assertStringOrNull(compiler, Sass::Strings::module); + MapObj list = SASS_MEMORY_NEW(Map, pstate); + auto module = compiler.getCurrentModule(); + auto it = module->module->moduse.find(ns->value()); + if (it != module->module->moduse.end()) { + EnvRefs* refs = it->second.first; + Module* root = it->second.second; + if (root && !root->isCompiled) { + throw Exception::RuntimeException(compiler, "There is " + "no module with namespace \"" + ns->value() + "\"."); + } + for (auto entry : refs->fnIdxs) { + auto name = SASS_MEMORY_NEW(String, pstate, + sass::string(entry.first.norm()), true); + EnvRef fidx(refs, entry.second); + auto callable = compiler.varRoot.getFunction(fidx); + auto fn = SASS_MEMORY_NEW(Function, pstate, callable); + list->insert({ name, fn }); + } + if (root) + for (auto entry : root->mergedFwdFn) { + auto name = SASS_MEMORY_NEW(String, pstate, + sass::string(entry.first.norm()), true); + EnvRef fidx(entry.second); + auto callable = compiler.varRoot.getFunction(fidx); + auto fn = SASS_MEMORY_NEW(Function, pstate, callable); + list->insert({ name, fn }); + } + } + else { + throw Exception::RuntimeException(compiler, "There is " + "no module with namespace \"" + ns->value() + "\"."); + } + return list.detach(); + } + + /*******************************************************************/ + + /// Like `_environment.findFunction`, but also returns built-in + /// globally-available functions. + Callable* _getFunction(const EnvKey& name, Compiler& ctx, const sass::string& ns = "") { + EnvRef fidx = ctx.varRoot.findFnIdx(name, ""); + if (!fidx.isValid()) return nullptr; + return ctx.varRoot.getFunction(fidx); + } + + /// Like `_environment.findFunction`, but also returns built-in + /// globally-available functions. + Callable* _getMixin(const EnvKey& name, Compiler& ctx, const sass::string& ns = "") { + EnvRef fidx = ctx.varRoot.findMixIdx(name, ""); + if (!fidx.isValid()) return nullptr; + return ctx.varRoot.getMixin(fidx); + } + + BUILT_IN_FN(findFunction) + { + + String* name = arguments[0]->assertString(compiler, Sass::Strings::name); + bool css = arguments[1]->isTruthy(); // supports all values + String* ns = arguments[2]->assertStringOrNull(compiler, Sass::Strings::module); + + if (css && ns != nullptr) { + throw Exception::RuntimeException(compiler, + "$css and $module may not both be passed at once."); + } + + if (css) { + return SASS_MEMORY_NEW(Function, pstate, name->value()); + } + + CallableObj callable; + + auto parent = compiler.getCurrentModule(); + + if (ns != nullptr) { + auto pp = parent->module->moduse.find(ns->value()); + if (pp != parent->module->moduse.end()) { + EnvRefs* module = pp->second.first; + auto it = module->fnIdxs.find(name->value()); + if (it != module->fnIdxs.end()) { + EnvRef fidx({ module, it->second }); + callable = compiler.varRoot.getFunction(fidx); + } + } + else { + throw Exception::RuntimeException(compiler, + "There is no module with the namespace \"" + ns->value() + "\"."); + } + } + else { + + callable = _getFunction(name->value(), compiler); + + if (!callable) { + + for (auto global : parent->forwards) { + auto it = global->fnIdxs.find(name->value()); + if (it != global->fnIdxs.end()) { + if (callable) { + throw Exception::RuntimeException(compiler, + "This function is available from multiple global modules."); + } + EnvRef fidx({ global, it->second }); + callable = compiler.varRoot.getFunction(fidx); + if (callable) break; + } + } + } + } + + + if (callable == nullptr) { + if (name->hasQuotes()) { + throw + Exception::RuntimeException(compiler, + "Function not found: \"" + name->value() + "\""); + } + else { + throw + Exception::RuntimeException(compiler, + "Function not found: " + name->value() + ""); + } + } + + return SASS_MEMORY_NEW(Function, pstate, callable); + + } + + + BUILT_IN_FN(findMixin) + { + + String* name = arguments[0]->assertString(compiler, Sass::Strings::name); + String* ns = arguments[1]->assertStringOrNull(compiler, Sass::Strings::module); + + //if (css && ns != nullptr) { + // throw Exception::RuntimeException(compiler, + // "$css and $module may not both be passed at once."); + //} + // + //if (css) { + // return SASS_MEMORY_NEW(Function, pstate, "HABA"+name->value()); + //} + + CallableObj callable; + + auto parent = compiler.getCurrentModule(); + + if (ns != nullptr) { + auto pp = parent->module->moduse.find(ns->value()); + if (pp != parent->module->moduse.end()) { + EnvRefs* module = pp->second.first; + auto it = module->mixIdxs.find(name->value()); + if (it != module->mixIdxs.end()) { + EnvRef fidx({ module, it->second }); + callable = compiler.varRoot.getMixin(fidx); + } + } + else { + throw Exception::RuntimeException(compiler, + "There is no module with the namespace \"" + ns->value() + "\"."); + } + } + else { + + callable = _getMixin(name->value(), compiler); + + if (!callable) { + + for (auto global : parent->forwards) { + auto it = global->mixIdxs.find(name->value()); + if (it != global->mixIdxs.end()) { + if (callable) { + throw Exception::RuntimeException(compiler, + "This mixin is available from multiple global modules."); + } + EnvRef fidx({ global, it->second }); + callable = compiler.varRoot.getMixin(fidx); + if (callable) break; + } + } + } + } + + + if (callable == nullptr) { + if (name->hasQuotes()) { + throw + Exception::RuntimeException(compiler, + "Mixin not found: \"" + name->value() + "\""); + } + else { + throw + Exception::RuntimeException(compiler, + "Mixin not found: " + name->value() + ""); + } + } + + return SASS_MEMORY_NEW(Mixin, pstate, callable); + + } + + /*******************************************************************/ + + BUILT_IN_FN(call) + { + + Value* function = arguments[0]->assertValue(compiler, "function"); + ArgumentList* args = arguments[1]->assertArgumentList(compiler, Sass::Strings::args); + + // ExpressionVector positional, + // EnvKeyMap named, + // Expression* restArgs = nullptr, + // Expression* kwdRest = nullptr); + + ValueExpression* restArg = SASS_MEMORY_NEW( + ValueExpression, args->pstate(), args); + + ValueExpression* kwdRest = nullptr; + if (!args->keywords().empty()) { + Map* map = args->keywordsAsSassMap(); + kwdRest = SASS_MEMORY_NEW( + ValueExpression, map->pstate(), map); + } + + CallableArgumentsObj invocation = SASS_MEMORY_NEW( + CallableArguments, pstate, ExpressionVector{}, {}, restArg, kwdRest); + + if (String * str = function->isaString()) { + sass::string name = str->value(); + compiler.addDeprecation( + "Passing a string to call() is deprecated and will be illegal in LibSass 4.1.0.\n" + "Use call(get-function(" + str->inspect() + ")) instead.", + str->pstate(), Logger::WARN_STRING_CALL); + + InterpolationObj itpl = SASS_MEMORY_NEW(Interpolation, pstate); + itpl->append(SASS_MEMORY_NEW(String, pstate, sass::string(str->value()))); + FunctionExpressionObj expression = SASS_MEMORY_NEW( + FunctionExpression, pstate, str->value(), invocation); + return eval.acceptFunctionExpression(expression); + + } + + Function* fn = function->assertFunction(compiler, "function"); + if (fn->cssName().empty()) { + return fn->callable()->execute(eval, invocation, pstate); + } + else { + sass::string strm; + strm += fn->cssName(); + eval.renderArgumentInvocation(strm, invocation); + return SASS_MEMORY_NEW( + String, fn->pstate(), + std::move(strm)); + } + + + } + + /*******************************************************************/ + + BUILT_IN_FN(loadCss) + { + String* url = arguments[0]->assertStringOrNull(compiler, Strings::url); + MapObj withMap = arguments[1]->assertMapOrNull(compiler, Strings::with); + + bool hasWith = withMap && !withMap->empty(); + + EnvKeyFlatMap config; + sass::vector withConfigs; + + if (hasWith) { + for (auto& kv : withMap->elements()) { + String* name = kv.first->assertString(compiler, "with key"); + EnvKey kname(name->value()); + WithConfigVar kvar; + kvar.name = name->value(); + kvar.value33 = kv.second; + kvar.isGuarded41 = false; + kvar.wasAssigned = false; + kvar.pstate = name->pstate(); + withConfigs.push_back(kvar); + if (config.count(kname) == 1) { + throw Exception::RuntimeException(compiler, + "The variable $" + kname.norm() + " was configured twice."); + } + config[name->value()] = kv.second; + } + } + + if (StringUtils::startsWith(url->value(), "sass:", 5)) { + + if (hasWith) { + throw Exception::RuntimeException(compiler, "Built-in " + "module " + url->value() + " can't be configured."); + } + + return SASS_MEMORY_NEW(Null, SourceSpan::internal("[LOADCSS]")); // pstate leaks?; + } + + WithConfig wconfig(compiler.wconfig, withConfigs, hasWith); + + WithConfig*& pwconfig(compiler.wconfig); + RAII_PTR(WithConfig, pwconfig, &wconfig); + + sass::string prev(pstate.getAbsPath()); + if (Root* sheet = eval.loadModule( + prev, url->value(), false)) { + + sheet->extender = eval.extender2; + + if (!sheet->isCompiled) { + ImportStackFrame iframe(compiler, sheet->import); + LocalOption scoped(compiler.hasWithConfig, + compiler.hasWithConfig || hasWith); + // RAII_PTR(Root, extctx33, root); + eval.compileModule(sheet); + wconfig.finalize(compiler); + } + else if (compiler.hasWithConfig || hasWith) { + throw Exception::ParserException(compiler, + sass::string(sheet->pstate().getImpPath()) + + " was already loaded, so it " + "can't be configured using \"with\"."); + } + eval.insertModule(sheet); + } + + return SASS_MEMORY_NEW(Null, SourceSpan::internal("[LOADCSS]")); // pstate leaks? + } + + /*******************************************************************/ + + void registerFunctions(Compiler& compiler) + { + + BuiltInMod& module(compiler.createModule("meta")); + + compiler.registerBuiltInFunction(key_if, "$condition, $if-true, $if-false", fnIf); + + module.addMixin(key_apply, compiler.createBuiltInMixin(key_apply, "$mixin, $args...", fnApply, true)); + module.addMixin(key_load_css, compiler.createBuiltInMixin(key_load_css, "$url, $with: null", loadCss)); + + module.addFunction(key_calc_name, compiler.registerBuiltInFunction(key_calc_name, "$calc", fnCalcName)); + module.addFunction(key_calc_args, compiler.registerBuiltInFunction(key_calc_args, "$calc", fnCalcArgs)); + module.addFunction(key_get_mixin, compiler.registerBuiltInFunction(key_get_mixin, "$name, $module: null", findMixin)); + module.addFunction(key_module_mixins, compiler.registerBuiltInFunction(key_module_mixins, "$module", fnModuleMixins)); + module.addFunction(key_accepts_content, compiler.registerBuiltInFunction(key_accepts_content, "$mixin", fnAcceptsContent)); + + module.addFunction(key_feature_exists, compiler.registerBuiltInFunction(key_feature_exists, "$feature", featureExists)); + module.addFunction(key_type_of, compiler.registerBuiltInFunction(key_type_of, "$value", typeOf)); + module.addFunction(key_inspect, compiler.registerBuiltInFunction(key_inspect, "$value", inspect)); + module.addFunction(key_keywords, compiler.registerBuiltInFunction(key_keywords, "$args", keywords)); + module.addFunction(key_global_variable_exists, compiler.registerBuiltInFunction(key_global_variable_exists, "$name, $module: null", globalVariableExists)); + module.addFunction(key_variable_exists, compiler.registerBuiltInFunction(key_variable_exists, "$name", variableExists)); + module.addFunction(key_function_exists, compiler.registerBuiltInFunction(key_function_exists, "$name, $module: null", functionExists)); + module.addFunction(key_mixin_exists, compiler.registerBuiltInFunction(key_mixin_exists, "$name, $module: null", mixinExists)); + module.addFunction(key_content_exists, compiler.registerBuiltInFunction(key_content_exists, "", contentExists)); + module.addFunction(key_module_variables, compiler.createBuiltInFunction(key_module_variables, "$module", moduleVariables)); + module.addFunction(key_module_functions, compiler.registerBuiltInFunction(key_module_functions, "$module", moduleFunctions)); + module.addFunction(key_get_function, compiler.registerBuiltInFunction(key_get_function, "$name, $css: false, $module: null", findFunction)); + module.addFunction(key_call, compiler.registerBuiltInFunction(key_call, "$function, $args...", call)); + + } + + /*******************************************************************/ + + } + + } + +} diff --git a/src/fn_meta.hpp b/src/fn_meta.hpp new file mode 100644 index 0000000000..d0a015b5f1 --- /dev/null +++ b/src/fn_meta.hpp @@ -0,0 +1,36 @@ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#ifndef SASS_FN_META_HPP +#define SASS_FN_META_HPP + +// sass.hpp must go before all system headers +// to get the __EXTENSIONS__ fix on Solaris. +#include "capi_sass.hpp" + +#include "fn_utils.hpp" + +namespace Sass { + + namespace Functions { + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + namespace Meta { + + Value* loadCss(FN_PROTOTYPE); + + // Register all functions at compiler + void registerFunctions(Compiler& ctx); + + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + } + +} + +#endif diff --git a/src/fn_miscs.cpp b/src/fn_miscs.cpp deleted file mode 100644 index d5e28ca6c4..0000000000 --- a/src/fn_miscs.cpp +++ /dev/null @@ -1,248 +0,0 @@ -#include "ast.hpp" -#include "expand.hpp" -#include "fn_utils.hpp" -#include "fn_miscs.hpp" -#include "util_string.hpp" - -namespace Sass { - - namespace Functions { - - ////////////////////////// - // INTROSPECTION FUNCTIONS - ////////////////////////// - - Signature type_of_sig = "type-of($value)"; - BUILT_IN(type_of) - { - Expression* v = ARG("$value", Expression); - return SASS_MEMORY_NEW(String_Quoted, pstate, v->type()); - } - - Signature variable_exists_sig = "variable-exists($name)"; - BUILT_IN(variable_exists) - { - sass::string s = Util::normalize_underscores(unquote(ARG("$name", String_Constant)->value())); - - if(d_env.has("$"+s)) { - return SASS_MEMORY_NEW(Boolean, pstate, true); - } - else { - return SASS_MEMORY_NEW(Boolean, pstate, false); - } - } - - Signature global_variable_exists_sig = "global-variable-exists($name)"; - BUILT_IN(global_variable_exists) - { - sass::string s = Util::normalize_underscores(unquote(ARG("$name", String_Constant)->value())); - - if(d_env.has_global("$"+s)) { - return SASS_MEMORY_NEW(Boolean, pstate, true); - } - else { - return SASS_MEMORY_NEW(Boolean, pstate, false); - } - } - - Signature function_exists_sig = "function-exists($name)"; - BUILT_IN(function_exists) - { - String_Constant* ss = Cast(env["$name"]); - if (!ss) { - error("$name: " + (env["$name"]->to_string()) + " is not a string for `function-exists'", pstate, traces); - } - - sass::string name = Util::normalize_underscores(unquote(ss->value())); - - if(d_env.has(name+"[f]")) { - return SASS_MEMORY_NEW(Boolean, pstate, true); - } - else { - return SASS_MEMORY_NEW(Boolean, pstate, false); - } - } - - Signature mixin_exists_sig = "mixin-exists($name)"; - BUILT_IN(mixin_exists) - { - sass::string s = Util::normalize_underscores(unquote(ARG("$name", String_Constant)->value())); - - if(d_env.has(s+"[m]")) { - return SASS_MEMORY_NEW(Boolean, pstate, true); - } - else { - return SASS_MEMORY_NEW(Boolean, pstate, false); - } - } - - Signature feature_exists_sig = "feature-exists($feature)"; - BUILT_IN(feature_exists) - { - sass::string s = unquote(ARG("$feature", String_Constant)->value()); - - static const auto *const features = new std::unordered_set { - "global-variable-shadowing", - "extend-selector-pseudoclass", - "at-error", - "units-level-3", - "custom-property" - }; - return SASS_MEMORY_NEW(Boolean, pstate, features->find(s) != features->end()); - } - - Signature call_sig = "call($function, $args...)"; - BUILT_IN(call) - { - sass::string function; - Function* ff = Cast(env["$function"]); - String_Constant* ss = Cast(env["$function"]); - - if (ss) { - function = Util::normalize_underscores(unquote(ss->value())); - std::cerr << "DEPRECATION WARNING: "; - std::cerr << "Passing a string to call() is deprecated and will be illegal" << std::endl; - std::cerr << "in Sass 4.0. Use call(get-function(" + quote(function) + ")) instead." << std::endl; - std::cerr << std::endl; - } else if (ff) { - function = ff->name(); - } - - List_Obj arglist = SASS_MEMORY_COPY(ARG("$args", List)); - - Arguments_Obj args = SASS_MEMORY_NEW(Arguments, pstate); - // sass::string full_name(name + "[f]"); - // Definition* def = d_env.has(full_name) ? Cast((d_env)[full_name]) : 0; - // Parameters* params = def ? def->parameters() : 0; - // size_t param_size = params ? params->length() : 0; - for (size_t i = 0, L = arglist->length(); i < L; ++i) { - ExpressionObj expr = arglist->value_at_index(i); - // if (params && params->has_rest_parameter()) { - // Parameter_Obj p = param_size > i ? (*params)[i] : 0; - // List* list = Cast(expr); - // if (list && p && !p->is_rest_parameter()) expr = (*list)[0]; - // } - if (arglist->is_arglist()) { - ExpressionObj obj = arglist->at(i); - Argument_Obj arg = (Argument*) obj.ptr(); // XXX - args->append(SASS_MEMORY_NEW(Argument, - pstate, - expr, - arg ? arg->name() : "", - arg ? arg->is_rest_argument() : false, - arg ? arg->is_keyword_argument() : false)); - } else { - args->append(SASS_MEMORY_NEW(Argument, pstate, expr)); - } - } - Function_Call_Obj func = SASS_MEMORY_NEW(Function_Call, pstate, function, args); - - Expand expand(ctx, &d_env, &selector_stack, &original_stack); - func->via_call(true); // calc invoke is allowed - if (ff) func->func(ff); - return Cast(func->perform(&expand.eval)); - } - - //////////////////// - // BOOLEAN FUNCTIONS - //////////////////// - - Signature not_sig = "not($value)"; - BUILT_IN(sass_not) - { - return SASS_MEMORY_NEW(Boolean, pstate, ARG("$value", Expression)->is_false()); - } - - Signature if_sig = "if($condition, $if-true, $if-false)"; - BUILT_IN(sass_if) - { - Expand expand(ctx, &d_env, &selector_stack, &original_stack); - ExpressionObj cond = ARG("$condition", Expression)->perform(&expand.eval); - bool is_true = !cond->is_false(); - ExpressionObj res = ARG(is_true ? "$if-true" : "$if-false", Expression); - ExpressionObj rv = res->perform(&expand.eval); - ValueObj value = Cast(rv); - if (value != nullptr) { - value->set_delayed(false); - return value.detach(); - } - rv->set_delayed(false); - return nullptr; - } - - ////////////////////////// - // MISCELLANEOUS FUNCTIONS - ////////////////////////// - - Signature inspect_sig = "inspect($value)"; - BUILT_IN(inspect) - { - Expression* v = ARG("$value", Expression); - if (v->concrete_type() == Expression::NULL_VAL) { - return SASS_MEMORY_NEW(String_Constant, pstate, "null"); - } else if (v->concrete_type() == Expression::BOOLEAN && v->is_false()) { - return SASS_MEMORY_NEW(String_Constant, pstate, "false"); - } else if (v->concrete_type() == Expression::STRING) { - String_Constant *s = Cast(v); - if (s->quote_mark()) { - return SASS_MEMORY_NEW(String_Constant, pstate, quote(s->value(), s->quote_mark())); - } else { - return s; - } - } else { - // ToDo: fix to_sass for nested parentheses - Sass_Output_Style old_style; - old_style = ctx.c_options.output_style; - ctx.c_options.output_style = TO_SASS; - Emitter emitter(ctx.c_options); - Inspect i(emitter); - i.in_declaration = false; - v->perform(&i); - ctx.c_options.output_style = old_style; - return SASS_MEMORY_NEW(String_Quoted, pstate, i.get_buffer()); - } - } - - Signature content_exists_sig = "content-exists()"; - BUILT_IN(content_exists) - { - if (!d_env.has_global("is_in_mixin")) { - error("Cannot call content-exists() except within a mixin.", pstate, traces); - } - return SASS_MEMORY_NEW(Boolean, pstate, d_env.has_lexical("@content[m]")); - } - - Signature get_function_sig = "get-function($name, $css: false)"; - BUILT_IN(get_function) - { - String_Constant* ss = Cast(env["$name"]); - if (!ss) { - error("$name: " + (env["$name"]->to_string()) + " is not a string for `get-function'", pstate, traces); - } - - sass::string name = Util::normalize_underscores(unquote(ss->value())); - sass::string full_name = name + "[f]"; - - Boolean_Obj css = ARG("$css", Boolean); - if (!css->is_false()) { - Definition* def = SASS_MEMORY_NEW(Definition, - pstate, - name, - SASS_MEMORY_NEW(Parameters, pstate), - SASS_MEMORY_NEW(Block, pstate, 0, false), - Definition::FUNCTION); - return SASS_MEMORY_NEW(Function, pstate, def, true); - } - - - if (!d_env.has_global(full_name)) { - error("Function not found: " + name, pstate, traces); - } - - Definition* def = Cast(d_env[full_name]); - return SASS_MEMORY_NEW(Function, pstate, def, false); - } - - } - -} diff --git a/src/fn_miscs.hpp b/src/fn_miscs.hpp deleted file mode 100644 index aec693e92d..0000000000 --- a/src/fn_miscs.hpp +++ /dev/null @@ -1,40 +0,0 @@ -#ifndef SASS_FN_MISCS_H -#define SASS_FN_MISCS_H - -#include "fn_utils.hpp" - -namespace Sass { - - namespace Functions { - - extern Signature type_of_sig; - extern Signature variable_exists_sig; - extern Signature global_variable_exists_sig; - extern Signature function_exists_sig; - extern Signature mixin_exists_sig; - extern Signature feature_exists_sig; - extern Signature call_sig; - extern Signature not_sig; - extern Signature if_sig; - extern Signature set_nth_sig; - extern Signature content_exists_sig; - extern Signature get_function_sig; - - BUILT_IN(type_of); - BUILT_IN(variable_exists); - BUILT_IN(global_variable_exists); - BUILT_IN(function_exists); - BUILT_IN(mixin_exists); - BUILT_IN(feature_exists); - BUILT_IN(call); - BUILT_IN(sass_not); - BUILT_IN(sass_if); - BUILT_IN(set_nth); - BUILT_IN(content_exists); - BUILT_IN(get_function); - - } - -} - -#endif diff --git a/src/fn_numbers.cpp b/src/fn_numbers.cpp deleted file mode 100644 index 73d78b0377..0000000000 --- a/src/fn_numbers.cpp +++ /dev/null @@ -1,246 +0,0 @@ -// sass.hpp must go before all system headers to get the -// __EXTENSIONS__ fix on Solaris. -#include "sass.hpp" - -#include -#include -#include -#include -#include -#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() - { - // Init universe entropy - uint64_t rnd = 42; - // Try to get random number from system - try { - std::random_device rd; - rnd = rd(); - } - // On certain system this can throw since either - // underlying hardware or software can be buggy. - // https://github.com/sass/libsass/issues/3151 - catch (std::exception&) { - } - // Don't trust anyone to be random, so we - // add a little entropy of our own. - rnd ^= std::time(NULL) ^ std::clock() ^ - std::hash() - (std::this_thread::get_id()); - // Return entropy - return rnd; - } - #endif - - // note: the performance of many implementations of - // random_device degrades sharply once the entropy pool - // is exhausted. For practical use, random_device is - // generally only used to seed a PRNG such as mt19937. - static std::mt19937 rand(static_cast(GetSeed())); - - /////////////////// - // NUMBER FUNCTIONS - /////////////////// - - Signature percentage_sig = "percentage($number)"; - BUILT_IN(percentage) - { - Number_Obj n = ARGN("$number"); - if (!n->is_unitless()) error("argument $number of `" + sass::string(sig) + "` must be unitless", pstate, traces); - return SASS_MEMORY_NEW(Number, pstate, n->value() * 100, "%"); - } - - Signature round_sig = "round($number)"; - BUILT_IN(round) - { - Number_Obj r = ARGN("$number"); - r->value(Sass::round(r->value(), ctx.c_options.precision)); - r->pstate(pstate); - return r.detach(); - } - - Signature ceil_sig = "ceil($number)"; - BUILT_IN(ceil) - { - Number_Obj r = ARGN("$number"); - r->value(std::ceil(r->value())); - r->pstate(pstate); - return r.detach(); - } - - 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) { - ExpressionObj val = arglist->value_at_index(i); - Number_Obj xi = Cast(val); - if (!xi) { - error("\"" + val->to_string(ctx.c_options) + "\" is not a number for `min'", pstate, traces); - } - 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) { - ExpressionObj val = arglist->value_at_index(i); - Number_Obj xi = Cast(val); - if (!xi) { - error("\"" + val->to_string(ctx.c_options) + "\" is not a number for `max'", pstate, traces); - } - 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) { - sass::ostream 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) { - sass::ostream 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) - { - sass::ostream ss; - std::uniform_real_distribution<> distributor(0, 4294967296); // 16^8 - uint_fast32_t distributed = static_cast(distributor(rand)); - ss << "u" << std::setfill('0') << std::setw(8) << std::hex << distributed; - return SASS_MEMORY_NEW(String_Quoted, pstate, ss.str()); - } - - Signature unit_sig = "unit($number)"; - BUILT_IN(unit) - { - Number_Obj arg = ARGN("$number"); - sass::string str(quote(arg->unit(), '"')); - return SASS_MEMORY_NEW(String_Quoted, pstate, str); - } - - 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/fn_numbers.hpp b/src/fn_numbers.hpp deleted file mode 100644 index dba96be0b4..0000000000 --- a/src/fn_numbers.hpp +++ /dev/null @@ -1,45 +0,0 @@ -#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/fn_selectors.cpp b/src/fn_selectors.cpp index 3a7f88b2e9..a3a92861a0 100644 --- a/src/fn_selectors.cpp +++ b/src/fn_selectors.cpp @@ -1,204 +1,239 @@ -#include +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#include "fn_selectors.hpp" -#include "parser.hpp" #include "extender.hpp" -#include "listize.hpp" -#include "fn_utils.hpp" -#include "fn_selectors.hpp" +#include "source.hpp" +#include "compiler.hpp" +#include "exceptions.hpp" +#include "ast_values.hpp" +#include "ast_selectors.hpp" +#include "parser_selector.hpp" namespace Sass { namespace Functions { - Signature selector_nest_sig = "selector-nest($selectors...)"; - BUILT_IN(selector_nest) - { - List* arglist = ARG("$selectors", List); - - // Not enough parameters - if (arglist->length() == 0) { - error( - "$selectors: At least one selector must be passed for `selector-nest'", - pstate, traces); + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // Adds a [ParentSelector] to the beginning of [compound], + // or returns `null` if that wouldn't produce a valid selector. + CompoundSelector* prependParent(CompoundSelector* compound) { + SimpleSelector* first = compound->first(); + if (first->isUniversal()) return nullptr; + if (TypeSelector * type = first->isaTypeSelector()) { + if (type->hasNs()) return nullptr; + CompoundSelector* copy = SASS_MEMORY_COPY(compound); + copy->withExplicitParent(true); + return copy; } - - // Parse args into vector of selectors - SelectorStack parsedSelectors; - for (size_t i = 0, L = arglist->length(); i < L; ++i) { - ExpressionObj exp = Cast(arglist->value_at_index(i)); - if (exp->concrete_type() == Expression::NULL_VAL) { - error( - "$selectors: null is not a valid selector: it must be a string,\n" - "a list of strings, or a list of lists of strings for 'selector-nest'", - pstate, traces); - } - if (String_Constant_Obj str = Cast(exp)) { - str->quote_mark(0); - } - sass::string exp_src = exp->to_string(ctx.c_options); - ItplFile* source = SASS_MEMORY_NEW(ItplFile, exp_src.c_str(), exp->pstate()); - SelectorListObj sel = Parser::parse_selector(source, ctx, traces); - parsedSelectors.push_back(sel); + else { + CompoundSelector* copy = SASS_MEMORY_COPY(compound); + copy->withExplicitParent(true); + return copy; } + } - // Nothing to do - if( parsedSelectors.empty() ) { - return SASS_MEMORY_NEW(Null, pstate); - } + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// - // Set the first element as the `result`, keep - // appending to as we go down the parsedSelector vector. - SelectorStack::iterator itr = parsedSelectors.begin(); - SelectorListObj& result = *itr; - ++itr; - - for(;itr != parsedSelectors.end(); ++itr) { - SelectorListObj& child = *itr; - original_stack.push_back(result); - SelectorListObj rv = child->resolve_parent_refs(original_stack, traces); - result->elements(rv->elements()); - original_stack.pop_back(); - } + namespace Selectors { - return Cast(Listize::perform(result)); - } - - Signature selector_append_sig = "selector-append($selectors...)"; - BUILT_IN(selector_append) - { - List* arglist = ARG("$selectors", List); - - // Not enough parameters - if (arglist->empty()) { - error( - "$selectors: At least one selector must be " - "passed for `selector-append'", - pstate, traces); - } + /*******************************************************************/ - // Parse args into vector of selectors - SelectorStack parsedSelectors; - parsedSelectors.push_back({}); - for (size_t i = 0, L = arglist->length(); i < L; ++i) { - Expression* exp = Cast(arglist->value_at_index(i)); - if (exp->concrete_type() == Expression::NULL_VAL) { - error( - "$selectors: null is not a valid selector: it must be a string,\n" - "a list of strings, or a list of lists of strings for 'selector-append'", - pstate, traces); + BUILT_IN_FN(nest) + { + // Not enough parameters + if (arguments[0]->lengthAsList() == 0) { + throw Exception::RuntimeException(compiler, + "$selectors: At least one selector must be passed."); } - if (String_Constant* str = Cast(exp)) { - str->quote_mark(0); + SelectorListObj result; + // Iterate over the rest argument list + for (Value* arg : arguments[0]->start()) { + if (arg->isNull()) { + callStackFrame csf(compiler, arg->pstate()); + throw Exception::RuntimeException(compiler, // "$selectors: " + "null is not a valid selector: it must be a string,\n" + "a list of strings, or a list of lists of strings."); + } + // Read and parse argument into selectors + SelectorListObj slist(arg->assertSelector( + compiler, Strings::empty, !result.isNull())); + // First item is just taken as it is + if (result.isNull()) { result = slist; continue; } + // Otherwise resolve it with the previous selector + result = slist->resolveParentSelectors(result, compiler); } - sass::string exp_src = exp->to_string(); - ItplFile* source = SASS_MEMORY_NEW(ItplFile, exp_src.c_str(), exp->pstate()); - SelectorListObj sel = Parser::parse_selector(source, ctx, traces, true); + // Convert and return value + return result->toValue(); + } - for (auto& complex : sel->elements()) { - if (complex->empty()) { - complex->append(SASS_MEMORY_NEW(CompoundSelector, "[append]")); - } - if (CompoundSelector* comp = Cast(complex->first())) { - comp->hasRealParent(true); - complex->chroots(true); - } + /*******************************************************************/ + + BUILT_IN_FN(append) + { + // Not enough parameters + if (arguments[0]->lengthAsList() == 0) { + throw Exception::RuntimeException(compiler, + "$selectors: At least one selector must be passed."); } - if (parsedSelectors.size() > 1) { + // return SASS_MEMORY_NEW(Null, pstate); - if (!sel->has_real_parent_ref()) { - auto parent = parsedSelectors.back(); - for (auto& complex : parent->elements()) { - if (CompoundSelector* comp = Cast(complex->first())) { - comp->hasRealParent(false); + SelectorListObj reduced; + for (Value* arg : arguments[0]->start()) { + if (arg->isNull()) { + throw Exception::RuntimeException( // "$selectors: " + "null is not a valid selector: it must be a string,\n" + "a list of strings, or a list of lists of strings.", + compiler, arg->pstate()); + } + SelectorListObj slist(arg->assertSelector( + compiler, Strings::empty, false)); + // First item is just taken as it is + if (reduced.isNull()) { reduced = slist; continue; } + + // Combine selector list with parent + SelectorListObj cp = SASS_MEMORY_COPY(slist); + for (ComplexSelector* complex : slist->elements()) { + if (!complex->leadingCombinators().empty()) { + throw Exception::RuntimeException(compiler, + "Can't append " + slist->inspect() + " to " + + reduced->inspect() + "."); + } + if (complex->empty()) continue; // TODO + CplxSelComponent* component = complex->first(); + if (CompoundSelector* compound = component->selector()) { + compound = prependParent(compound); + if (compound == nullptr) { + throw Exception::RuntimeException(compiler, + "Can't append " + slist->inspect() + " to " + + reduced->inspect() + "."); } + complex->set(0, compound->wrapInComponent(component->combinators())); + } + else { + throw Exception::RuntimeException(compiler, + "Can't append " + slist->inspect() + " to " + + reduced->inspect() + "."); } - error("Can't append \"" + sel->to_string() + "\" to \"" + - parent->to_string() + "\" for `selector-append'", - pstate, traces); } - // Build the resolved stack from the left. It's cheaper to directly - // calculate and update each resolved selcted from the left, than to - // recursively calculate them from the right side, as we would need - // to go through the whole stack depth to build the final results. - // E.g. 'a', 'b', 'x, y' => 'a' => 'a b' => 'a b x, a b y' - // vs 'a', 'b', 'x, y' => 'x' => 'b x' => 'a b x', 'y' ... - parsedSelectors.push_back(sel->resolve_parent_refs(parsedSelectors, traces, true)); - } - else { - parsedSelectors.push_back(sel); + // Otherwise resolve it with the previous selector + reduced = cp->resolveParentSelectors(reduced, compiler, false); + } + + return reduced->toValue(); } - // Nothing to do - if( parsedSelectors.empty() ) { - return SASS_MEMORY_NEW(Null, pstate); + /*******************************************************************/ + + BUILT_IN_FN(extend) + { + SelectorListObj selector = arguments[0]-> + assertSelector(compiler, "selector") + ->assertNotBogus("selector"); + SelectorListObj target = arguments[1]-> + assertSelector(compiler, "extendee") + ->assertNotBogus("extendee"); + SelectorListObj source = arguments[2]-> + assertSelector(compiler, "extender") + ->assertNotBogus("extender"); + SelectorListObj result = ExtensionStore::extend(selector, source, target, compiler); + if (result.isNull()) return SASS_MEMORY_NEW(Null, pstate); + return result->toValue(); } - return Cast(Listize::perform(parsedSelectors.back())); - } + BUILT_IN_FN(replace) + { + SelectorListObj selector = arguments[0]-> + assertSelector(compiler, "selector") + ->assertNotBogus("selector"); + SelectorListObj target = arguments[1]-> + assertSelector(compiler, "original") + ->assertNotBogus("original"); + SelectorListObj source = arguments[2]-> + assertSelector(compiler, "replacement") + ->assertNotBogus("replacement"); + SelectorListObj result = ExtensionStore::replace(selector, source, target, compiler); + if (result.isNull()) return SASS_MEMORY_NEW(Null, pstate); + return result->toValue(); + } - Signature selector_unify_sig = "selector-unify($selector1, $selector2)"; - BUILT_IN(selector_unify) - { - SelectorListObj selector1 = ARGSELS("$selector1"); - SelectorListObj selector2 = ARGSELS("$selector2"); - SelectorListObj result = selector1->unifyWith(selector2); - return Cast(Listize::perform(result)); - } + BUILT_IN_FN(unify) + { + SelectorListObj selector1 = arguments[0]-> + assertSelector(compiler, "selector1"); + SelectorListObj selector2 = arguments[1]-> + assertSelector(compiler, "selector2"); + //std::cerr << "selector1 " << selector1->inspect() << "\n"; + //std::cerr << "selector2 " << selector2->inspect() << "\n"; + SelectorListObj result = selector1->unifyWith(selector2); + if (result.isNull()) return SASS_MEMORY_NEW(Null, pstate); + return result->toValue(); + } - Signature simple_selectors_sig = "simple-selectors($selector)"; - BUILT_IN(simple_selectors) - { - CompoundSelectorObj sel = ARGSEL("$selector"); + BUILT_IN_FN(isSuper) + { + SelectorListObj sel_sup = arguments[0] + ->assertSelector(compiler, "super") + ->assertNotBogus("super"); + + SelectorListObj sel_sub = arguments[1] + ->assertSelector(compiler, "sub") + ->assertNotBogus("super"); + bool result = sel_sup->isSuperselectorOf(sel_sub); + return SASS_MEMORY_NEW(Boolean, pstate, result); + } - List* l = SASS_MEMORY_NEW(List, sel->pstate(), sel->length(), SASS_COMMA); + BUILT_IN_FN(simple) + { + CompoundSelectorObj selector = arguments[0]-> + assertCompoundSelector(compiler, "selector"); + ValueVector results; + for (auto child : selector->elements()) { + results.emplace_back(SASS_MEMORY_NEW(String, + child->pstate(), child->inspect())); + } + // Return new value list + return SASS_MEMORY_NEW(List, + selector->pstate(), + std::move(results), + SASS_COMMA); + } - for (size_t i = 0, L = sel->length(); i < L; ++i) { - const SimpleSelectorObj& ss = sel->get(i); - sass::string ss_string = ss->to_string() ; - l->append(SASS_MEMORY_NEW(String_Quoted, ss->pstate(), ss_string)); + BUILT_IN_FN(parse) + { + SelectorListObj selector = arguments[0]-> + assertSelector(compiler, "selector"); + return selector->toValue(); } - return l; - } + /*******************************************************************/ - Signature selector_extend_sig = "selector-extend($selector, $extendee, $extender)"; - BUILT_IN(selector_extend) - { - SelectorListObj selector = ARGSELS("$selector"); - SelectorListObj target = ARGSELS("$extendee"); - SelectorListObj source = ARGSELS("$extender"); - SelectorListObj result = Extender::extend(selector, source, target, traces); - return Cast(Listize::perform(result)); - } + void registerFunctions(Compiler& ctx) + { + BuiltInMod& module(ctx.createModule("selector")); + module.addFunction(key_nest, ctx.registerBuiltInFunction(key_selector_nest, "$selectors...", nest)); + module.addFunction(key_append, ctx.registerBuiltInFunction(key_selector_append, "$selectors...", append)); + module.addFunction(key_extend, ctx.registerBuiltInFunction(key_selector_extend, "$selector, $extendee, $extender", extend)); + module.addFunction(key_replace, ctx.registerBuiltInFunction(key_selector_replace, "$selector, $original, $replacement", replace)); + module.addFunction(key_unify, ctx.registerBuiltInFunction(key_selector_unify, "$selector1, $selector2", unify)); + module.addFunction(key_is_superselector, ctx.registerBuiltInFunction(key_is_superselector, "$super, $sub", isSuper)); + module.addFunction(key_simple_selectors, ctx.registerBuiltInFunction(key_simple_selectors, "$selector", simple)); + module.addFunction(key_parse, ctx.registerBuiltInFunction(key_selector_parse, "$selector", parse)); + } - Signature selector_replace_sig = "selector-replace($selector, $original, $replacement)"; - BUILT_IN(selector_replace) - { - SelectorListObj selector = ARGSELS("$selector"); - SelectorListObj target = ARGSELS("$original"); - SelectorListObj source = ARGSELS("$replacement"); - SelectorListObj result = Extender::replace(selector, source, target, traces); - return Cast(Listize::perform(result)); - } + /*******************************************************************/ - Signature selector_parse_sig = "selector-parse($selector)"; - BUILT_IN(selector_parse) - { - SelectorListObj selector = ARGSELS("$selector"); - return Cast(Listize::perform(selector)); } - Signature is_superselector_sig = "is-superselector($super, $sub)"; - BUILT_IN(is_superselector) - { - SelectorListObj sel_sup = ARGSELS("$super"); - SelectorListObj sel_sub = ARGSELS("$sub"); - bool result = sel_sup->isSuperselectorOf(sel_sub); - return SASS_MEMORY_NEW(Boolean, pstate, result); - } + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// } diff --git a/src/fn_selectors.hpp b/src/fn_selectors.hpp index d5c106cd2b..95c9e8a444 100644 --- a/src/fn_selectors.hpp +++ b/src/fn_selectors.hpp @@ -1,5 +1,12 @@ -#ifndef SASS_FN_SELECTORS_H -#define SASS_FN_SELECTORS_H +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#ifndef SASS_FN_SELECTORS_HPP +#define SASS_FN_SELECTORS_HPP + +// sass.hpp must go before all system headers +// to get the __EXTENSIONS__ fix on Solaris. +#include "capi_sass.hpp" #include "fn_utils.hpp" @@ -7,26 +14,18 @@ namespace Sass { namespace Functions { - #define ARGSEL(argname) get_arg_sel(argname, env, sig, pstate, traces, ctx) - #define ARGSELS(argname) get_arg_sels(argname, env, sig, pstate, traces, ctx) - - BUILT_IN(selector_nest); - BUILT_IN(selector_append); - BUILT_IN(selector_extend); - BUILT_IN(selector_replace); - BUILT_IN(selector_unify); - BUILT_IN(is_superselector); - BUILT_IN(simple_selectors); - BUILT_IN(selector_parse); - - extern Signature selector_nest_sig; - extern Signature selector_append_sig; - extern Signature selector_extend_sig; - extern Signature selector_replace_sig; - extern Signature selector_unify_sig; - extern Signature is_superselector_sig; - extern Signature simple_selectors_sig; - extern Signature selector_parse_sig; + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + namespace Selectors { + + // Register all functions at compiler + void registerFunctions(Compiler& ctx); + + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// } diff --git a/src/fn_strings.cpp b/src/fn_strings.cpp deleted file mode 100644 index 58bf60733d..0000000000 --- a/src/fn_strings.cpp +++ /dev/null @@ -1,268 +0,0 @@ -// sass.hpp must go before all system headers to get the -// __EXTENSIONS__ fix on Solaris. -#include "sass.hpp" - -#include "utf8.h" -#include "ast.hpp" -#include "fn_utils.hpp" -#include "fn_strings.hpp" -#include "util_string.hpp" - -namespace Sass { - - namespace Functions { - - void handle_utf8_error (const SourceSpan& pstate, Backtraces traces) - { - try { - throw; - } - catch (utf8::invalid_code_point&) { - sass::string msg("utf8::invalid_code_point"); - error(msg, pstate, traces); - } - catch (utf8::not_enough_room&) { - sass::string msg("utf8::not_enough_room"); - error(msg, pstate, traces); - } - catch (utf8::invalid_utf8&) { - sass::string msg("utf8::invalid_utf8"); - error(msg, pstate, traces); - } - catch (...) { throw; } - } - - /////////////////// - // STRING FUNCTIONS - /////////////////// - - Signature unquote_sig = "unquote($string)"; - BUILT_IN(sass_unquote) - { - AST_Node_Obj arg = env["$string"]; - if (String_Quoted* string_quoted = Cast(arg)) { - String_Constant* result = SASS_MEMORY_NEW(String_Constant, pstate, string_quoted->value()); - // remember if the string was quoted (color tokens) - result->is_delayed(true); // delay colors - return result; - } - else if (String_Constant* str = Cast(arg)) { - return str; - } - else if (Value* ex = Cast(arg)) { - Sass_Output_Style oldstyle = ctx.c_options.output_style; - ctx.c_options.output_style = SASS_STYLE_NESTED; - sass::string val(arg->to_string(ctx.c_options)); - val = Cast(arg) ? "null" : val; - ctx.c_options.output_style = oldstyle; - - deprecated_function("Passing " + val + ", a non-string value, to unquote()", pstate); - return ex; - } - throw std::runtime_error("Invalid Data Type for unquote"); - } - - Signature quote_sig = "quote($string)"; - BUILT_IN(sass_quote) - { - const String_Constant* s = ARG("$string", String_Constant); - String_Quoted *result = SASS_MEMORY_NEW( - String_Quoted, pstate, s->value(), - /*q=*/'\0', /*keep_utf8_escapes=*/false, /*skip_unquoting=*/true); - result->quote_mark('*'); - return result; - } - - Signature str_length_sig = "str-length($string)"; - BUILT_IN(str_length) - { - size_t len = sass::string::npos; - try { - String_Constant* s = ARG("$string", String_Constant); - len = UTF_8::code_point_count(s->value(), 0, s->value().size()); - - } - // handle any invalid utf8 errors - // other errors will be re-thrown - catch (...) { handle_utf8_error(pstate, traces); } - // return something even if we had an error (-1) - return SASS_MEMORY_NEW(Number, pstate, (double)len); - } - - Signature str_insert_sig = "str-insert($string, $insert, $index)"; - BUILT_IN(str_insert) - { - sass::string str; - try { - String_Constant* s = ARG("$string", String_Constant); - str = s->value(); - String_Constant* i = ARG("$insert", String_Constant); - sass::string ins = i->value(); - double index = ARGVAL("$index"); - if (index != (int)index) { - sass::ostream strm; - strm << "$index: "; - strm << std::to_string(index); - strm << " is not an int"; - error(strm.str(), pstate, traces); - } - size_t len = UTF_8::code_point_count(str, 0, str.size()); - - if (index > 0 && index <= len) { - // positive and within string length - str.insert(UTF_8::offset_at_position(str, static_cast(index) - 1), ins); - } - else if (index > len) { - // positive and past string length - str += ins; - } - else if (index == 0) { - str = ins + str; - } - else if (std::abs(index) <= len) { - // negative and within string length - index += len + 1; - str.insert(UTF_8::offset_at_position(str, static_cast(index)), ins); - } - else { - // negative and past string length - str = ins + str; - } - - if (String_Quoted* ss = Cast(s)) { - if (ss->quote_mark()) str = quote(str); - } - } - // handle any invalid utf8 errors - // other errors will be re-thrown - catch (...) { handle_utf8_error(pstate, traces); } - return SASS_MEMORY_NEW(String_Quoted, pstate, str); - } - - Signature str_index_sig = "str-index($string, $substring)"; - BUILT_IN(str_index) - { - size_t index = sass::string::npos; - try { - String_Constant* s = ARG("$string", String_Constant); - String_Constant* t = ARG("$substring", String_Constant); - sass::string str = s->value(); - sass::string substr = t->value(); - - size_t c_index = str.find(substr); - if(c_index == sass::string::npos) { - return SASS_MEMORY_NEW(Null, pstate); - } - index = UTF_8::code_point_count(str, 0, c_index) + 1; - } - // handle any invalid utf8 errors - // other errors will be re-thrown - catch (...) { handle_utf8_error(pstate, traces); } - // return something even if we had an error (-1) - return SASS_MEMORY_NEW(Number, pstate, (double)index); - } - - Signature str_slice_sig = "str-slice($string, $start-at, $end-at:-1)"; - BUILT_IN(str_slice) - { - sass::string newstr; - try { - String_Constant* s = ARG("$string", String_Constant); - double start_at = ARGVAL("$start-at"); - double end_at = ARGVAL("$end-at"); - - if (start_at != (int)start_at) { - sass::ostream strm; - strm << "$start-at: "; - strm << std::to_string(start_at); - strm << " is not an int"; - error(strm.str(), pstate, traces); - } - - String_Quoted* ss = Cast(s); - - sass::string str(s->value()); - - size_t size = utf8::distance(str.begin(), str.end()); - - if (!Cast(env["$end-at"])) { - end_at = -1; - } - - if (end_at != (int)end_at) { - sass::ostream strm; - strm << "$end-at: "; - strm << std::to_string(end_at); - strm << " is not an int"; - error(strm.str(), pstate, traces); - } - - if (end_at == 0 || (end_at + size) < 0) { - if (ss && ss->quote_mark()) newstr = quote(""); - return SASS_MEMORY_NEW(String_Quoted, pstate, newstr); - } - - if (end_at < 0) { - end_at += size + 1; - if (end_at == 0) end_at = 1; - } - if (end_at > size) { end_at = (double)size; } - if (start_at < 0) { - start_at += size + 1; - if (start_at <= 0) start_at = 1; - } - else if (start_at == 0) { ++ start_at; } - - if (start_at <= end_at) - { - sass::string::iterator start = str.begin(); - utf8::advance(start, start_at - 1, str.end()); - sass::string::iterator end = start; - utf8::advance(end, end_at - start_at + 1, str.end()); - newstr = sass::string(start, end); - } - if (ss) { - if(ss->quote_mark()) newstr = quote(newstr); - } - } - // handle any invalid utf8 errors - // other errors will be re-thrown - catch (...) { handle_utf8_error(pstate, traces); } - return SASS_MEMORY_NEW(String_Quoted, pstate, newstr); - } - - Signature to_upper_case_sig = "to-upper-case($string)"; - BUILT_IN(to_upper_case) - { - String_Constant* s = ARG("$string", String_Constant); - sass::string str = s->value(); - Util::ascii_str_toupper(&str); - - if (String_Quoted* ss = Cast(s)) { - String_Quoted* cpy = SASS_MEMORY_COPY(ss); - cpy->value(str); - return cpy; - } else { - return SASS_MEMORY_NEW(String_Quoted, pstate, str); - } - } - - Signature to_lower_case_sig = "to-lower-case($string)"; - BUILT_IN(to_lower_case) - { - String_Constant* s = ARG("$string", String_Constant); - sass::string str = s->value(); - Util::ascii_str_tolower(&str); - - if (String_Quoted* ss = Cast(s)) { - String_Quoted* cpy = SASS_MEMORY_COPY(ss); - cpy->value(str); - return cpy; - } else { - return SASS_MEMORY_NEW(String_Quoted, pstate, str); - } - } - - } - -} diff --git a/src/fn_strings.hpp b/src/fn_strings.hpp deleted file mode 100644 index 4a1ed19009..0000000000 --- a/src/fn_strings.hpp +++ /dev/null @@ -1,34 +0,0 @@ -#ifndef SASS_FN_STRINGS_H -#define SASS_FN_STRINGS_H - -#include "fn_utils.hpp" - -namespace Sass { - - namespace Functions { - - extern Signature unquote_sig; - extern Signature quote_sig; - extern Signature str_length_sig; - extern Signature str_insert_sig; - extern Signature str_index_sig; - extern Signature str_slice_sig; - extern Signature to_upper_case_sig; - extern Signature to_lower_case_sig; - extern Signature length_sig; - - BUILT_IN(sass_unquote); - BUILT_IN(sass_quote); - BUILT_IN(str_length); - BUILT_IN(str_insert); - BUILT_IN(str_index); - BUILT_IN(str_slice); - BUILT_IN(to_upper_case); - BUILT_IN(to_lower_case); - BUILT_IN(length); - - } - -} - -#endif diff --git a/src/fn_texts.cpp b/src/fn_texts.cpp new file mode 100644 index 0000000000..43318426dd --- /dev/null +++ b/src/fn_texts.cpp @@ -0,0 +1,245 @@ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#include "fn_texts.hpp" + +#include +#include "unicode.hpp" +#include "compiler.hpp" +#include "exceptions.hpp" +#include "ast_values.hpp" + +#ifdef __MINGW32__ +#include "windows.h" +#include "wincrypt.h" +#endif + +namespace Sass { + + namespace Functions { + + namespace Texts { + + long _codepointForIndex(long index, long lengthInCodepoints, bool allowNegative = false) { + if (index == 0) return 0; + if (index > 0) return std::min(index - 1, lengthInCodepoints); + long result = lengthInCodepoints + index; + if (result < 0 && !allowNegative) return 0; + return result; + } + + BUILT_IN_FN(unquote) + { + String* string = arguments[0]->assertString(compiler, "string"); + if (!string->hasQuotes()) return string; + sass::string copy(string->value()); + return SASS_MEMORY_NEW(String, + string->pstate(), std::move(copy), false); + } + + BUILT_IN_FN(quote) + { + if (Color* col = arguments[0]->isaColor()) { + if (!col->disp().empty()) { + sass::string copy(col->disp()); + return SASS_MEMORY_NEW(String, + arguments[0]->pstate(), std::move(copy), true); + } + } + String* string = arguments[0]->assertString(compiler, "string"); + if (string->hasQuotes()) return string; + sass::string copy(string->value()); + return SASS_MEMORY_NEW(String, + string->pstate(), std::move(copy), true); + } + + BUILT_IN_FN(toUpperCase) + { + String* string = arguments[0]->assertString(compiler, "string"); + return SASS_MEMORY_NEW(String, pstate, + StringUtils::toUpperCase(string->value()), + string->hasQuotes()); + } + + BUILT_IN_FN(toLowerCase) + { + String* string = arguments[0]->assertString(compiler, "string"); + return SASS_MEMORY_NEW(String, pstate, + StringUtils::toLowerCase(string->value()), + string->hasQuotes()); + } + + BUILT_IN_FN(length) + { + String* string = arguments[0]->assertString(compiler, "string"); + size_t len = Unicode::codePointCount(string->value()); + return SASS_MEMORY_NEW(Number, pstate, (double)len); + } + + BUILT_IN_FN(insert) + { + String* string = arguments[0]->assertString(compiler, "string"); + String* insert = arguments[1]->assertString(compiler, "insert"); + size_t len = Unicode::codePointCount(string->value()); + long index = arguments[2]->assertNumber(compiler, "index") + ->assertUnitless(compiler, "index") + ->assertInt(compiler, "index"); + + // str-insert has unusual behavior for negative inputs. It guarantees that + // the `$insert` string is at `$index` in the result, which means that we + // want to insert before `$index` if it's positive and after if it's + // negative. + if (index < 0) { + // +1 because negative indexes start counting from -1 rather than 0, and + // another +1 because we want to insert *after* that index. + index = (long)std::fmax((long)len + index + 2, 0); + } + + index = (long) _codepointForIndex(index, (long)len); + + sass::string str(string->value()); + str.insert(Unicode::byteOffsetAtPosition( + str, index), insert->value()); + + return SASS_MEMORY_NEW(String, + pstate, std::move(str), + string->hasQuotes()); + } + + BUILT_IN_FN(index) + { + String* string = arguments[0]->assertString(compiler, "string"); + String* substring = arguments[1]->assertString(compiler, "substring"); + + sass::string str(string->value()); + sass::string substr(substring->value()); + + size_t c_index = str.find(substr); + if (c_index == sass::string::npos) { + return SASS_MEMORY_NEW(Null, pstate); + } + + return SASS_MEMORY_NEW(Number, pstate, + (double)Unicode::codePointCount(str, c_index) + 1); + } + + BUILT_IN_FN(slice) + { + String* string = arguments[0]->assertString(compiler, "string"); + Number* beg = arguments[1]->assertNumber(compiler, "start-at"); + Number* end = arguments[2]->assertNumber(compiler, "end-at"); + size_t len = Unicode::codePointCount(string->value()); + beg = beg->assertUnitless(compiler, "start-at"); + end = end->assertUnitless(compiler, "end-at"); + long begInt = beg->assertInt(compiler, "start-at"); + long endInt = end->assertInt(compiler, "end-at"); + + // No matter what the start index is, an end + // index of 0 will produce an empty string. + if (endInt == 0) { + return SASS_MEMORY_NEW(String, + pstate, "", string->hasQuotes()); + } + + begInt = (long)_codepointForIndex(begInt, (long)len, false); + endInt = (long)_codepointForIndex(endInt, (long)len, true); + + if (endInt == (long)len) endInt = (long)len - 1; + if (endInt < begInt) { + return SASS_MEMORY_NEW(String, + pstate, "", string->hasQuotes()); + } + + const sass::string& value(string->value()); + sass::string::const_iterator begIt = value.begin(); + sass::string::const_iterator endIt = value.begin(); + utf8::advance(begIt, begInt + 0, value.end()); + utf8::advance(endIt, endInt + 1, value.end()); + + return SASS_MEMORY_NEW( + String, pstate, + sass::string(begIt, endIt), + string->hasQuotes()); + + } + + BUILT_IN_FN(split) + { + String* string = arguments[0]->assertString(compiler, "string"); + String* separator = arguments[1]->assertString(compiler, "separator"); + Number* limit = arguments[2]->assertNumberOrNull(compiler, "limit"); + long limiter = std::numeric_limits().max(); + if (limit != nullptr) { + limiter = limit->assertInt(compiler, "limit"); + if (limiter < 1) throw Exception::SassScriptException( + "$limit: Must be 1 or greater, was " + limit->toString() + ".", + compiler, pstate); + } + const sass::string& text = string->value(); + const sass::string& delim = separator->value(); + bool quoted = string->hasQuotes(); + size_t start = 0; + ValueVector results; + auto begin = text.begin(); + // Have something to split? + if (text.empty()) {} + // And something to split by? + else if (delim.empty()) { + auto cur = begin; + while (cur != text.end()) { + utf8::advance(cur, 1, text.end()); + results.push_back(SASS_MEMORY_NEW(String, pstate, + sass::string(begin, cur), quoted)); + begin = cur; + } + } + else { + for (size_t found = text.find(delim); + found != sass::string::npos + && (long)results.size() < limiter; + found = text.find(delim, start)) + { + results.push_back(SASS_MEMORY_NEW(String, pstate, + sass::string(begin + start, begin + found), quoted)); + start = found + delim.size(); + } + if (start != text.size()) { + results.push_back(SASS_MEMORY_NEW(String, pstate, + sass::string(begin + start, text.end()), quoted)); + + } + } + return SASS_MEMORY_NEW(List, pstate, + std::move(results), SASS_COMMA, true); + } + + BUILT_IN_FN(uniqueId) + { + sass::sstream ss; ss << "u" + << std::setfill('0') << std::setw(8) + << std::hex << getRandomUint32(); + return SASS_MEMORY_NEW(String, + pstate, ss.str(), true); + } + + void registerFunctions(Compiler& ctx) + { + BuiltInMod& module(ctx.createModule("string")); + module.addFunction(key_unquote, ctx.registerBuiltInFunction(key_unquote, "$string", unquote)); + module.addFunction(key_quote, ctx.registerBuiltInFunction(key_quote, "$string", quote)); + module.addFunction(key_to_upper_case, ctx.registerBuiltInFunction(key_to_upper_case, "$string", toUpperCase)); + module.addFunction(key_to_lower_case, ctx.registerBuiltInFunction(key_to_lower_case, "$string", toLowerCase)); + module.addFunction(key_length, ctx.registerBuiltInFunction(key_str_length, "$string", length)); + module.addFunction(key_insert, ctx.registerBuiltInFunction(key_str_insert, "$string, $insert, $index", insert)); + module.addFunction(key_index, ctx.registerBuiltInFunction(key_str_index, "$string, $substring", index)); + module.addFunction(key_slice, ctx.registerBuiltInFunction(key_str_slice, "$string, $start-at, $end-at: -1", slice)); + module.addFunction(key_split, ctx.createBuiltInFunction(key_str_split, "$string, $separator, $limit: null", split)); + module.addFunction(key_unique_id, ctx.registerBuiltInFunction(key_unique_id, "", uniqueId)); + + } + + } + + } + +} diff --git a/src/fn_texts.hpp b/src/fn_texts.hpp new file mode 100644 index 0000000000..fe6f310638 --- /dev/null +++ b/src/fn_texts.hpp @@ -0,0 +1,34 @@ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#ifndef SASS_FN_TEXTS_HPP +#define SASS_FN_TEXTS_HPP + +// sass.hpp must go before all system headers +// to get the __EXTENSIONS__ fix on Solaris. +#include "capi_sass.hpp" + +#include "fn_utils.hpp" + +namespace Sass { + + namespace Functions { + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + namespace Texts { + + // Register all functions at compiler + void registerFunctions(Compiler& ctx); + + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + } + +} + +#endif diff --git a/src/fn_utils.cpp b/src/fn_utils.cpp deleted file mode 100644 index bcf5363973..0000000000 --- a/src/fn_utils.cpp +++ /dev/null @@ -1,158 +0,0 @@ -// sass.hpp must go before all system headers to get the -// __EXTENSIONS__ fix on Solaris. -#include "sass.hpp" - -#include "parser.hpp" -#include "fn_utils.hpp" -#include "util_string.hpp" - -namespace Sass { - - Definition* make_native_function(Signature sig, Native_Function func, Context& ctx) - { - SourceFile* source = SASS_MEMORY_NEW(SourceFile, "[built-in function]", sig, std::string::npos); - Parser sig_parser(source, ctx, ctx.traces); - sig_parser.lex(); - sass::string name(Util::normalize_underscores(sig_parser.lexed)); - Parameters_Obj params = sig_parser.parse_parameters(); - return SASS_MEMORY_NEW(Definition, - SourceSpan(source), - sig, - name, - params, - func, - false); - } - - Definition* make_c_function(Sass_Function_Entry c_func, Context& ctx) - { - using namespace Prelexer; - const char* sig = sass_function_get_signature(c_func); - SourceFile* source = SASS_MEMORY_NEW(SourceFile, "[c function]", sig, std::string::npos); - Parser sig_parser(source, ctx, ctx.traces); - // allow to overload generic callback plus @warn, @error and @debug with custom functions - sig_parser.lex < alternatives < identifier, exactly <'*'>, - exactly < Constants::warn_kwd >, - exactly < Constants::error_kwd >, - exactly < Constants::debug_kwd > - > >(); - sass::string name(Util::normalize_underscores(sig_parser.lexed)); - Parameters_Obj params = sig_parser.parse_parameters(); - return SASS_MEMORY_NEW(Definition, - SourceSpan(source), - sig, - name, - params, - c_func); - } - - namespace Functions { - - sass::string function_name(Signature sig) - { - sass::string str(sig); - return str.substr(0, str.find('(')); - } - - Map* get_arg_m(const sass::string& argname, Env& env, Signature sig, SourceSpan pstate, Backtraces traces) - { - AST_Node* value = env[argname]; - if (Map* map = Cast(value)) return map; - List* list = Cast(value); - if (list && list->length() == 0) { - return SASS_MEMORY_NEW(Map, pstate, 0); - } - return get_arg(argname, env, sig, pstate, traces); - } - - double get_arg_r(const sass::string& argname, Env& env, Signature sig, SourceSpan pstate, Backtraces traces, double lo, double hi) - { - Number* val = get_arg(argname, env, sig, pstate, traces); - Number tmpnr(val); - tmpnr.reduce(); - double v = tmpnr.value(); - if (!(lo <= v && v <= hi)) { - sass::ostream msg; - msg << "argument `" << argname << "` of `" << sig << "` must be between "; - msg << lo << " and " << hi; - error(msg.str(), pstate, traces); - } - return v; - } - - Number* get_arg_n(const sass::string& argname, Env& env, Signature sig, SourceSpan pstate, Backtraces traces) - { - Number* val = get_arg(argname, env, sig, pstate, traces); - val = SASS_MEMORY_COPY(val); - val->reduce(); - return val; - } - - double get_arg_val(const sass::string& argname, Env& env, Signature sig, SourceSpan pstate, Backtraces traces) - { - Number* val = get_arg(argname, env, sig, pstate, traces); - Number tmpnr(val); - tmpnr.reduce(); - return tmpnr.value(); - } - - double color_num(const sass::string& argname, Env& env, Signature sig, SourceSpan pstate, Backtraces traces) - { - Number* val = get_arg(argname, env, sig, pstate, traces); - Number tmpnr(val); - tmpnr.reduce(); - if (tmpnr.unit() == "%") { - return std::min(std::max(tmpnr.value() * 255 / 100.0, 0.0), 255.0); - } else { - return std::min(std::max(tmpnr.value(), 0.0), 255.0); - } - } - - double alpha_num(const sass::string& argname, Env& env, Signature sig, SourceSpan pstate, Backtraces traces) { - Number* val = get_arg(argname, env, sig, pstate, traces); - Number tmpnr(val); - tmpnr.reduce(); - if (tmpnr.unit() == "%") { - return std::min(std::max(tmpnr.value(), 0.0), 100.0); - } else { - return std::min(std::max(tmpnr.value(), 0.0), 1.0); - } - } - - SelectorListObj get_arg_sels(const sass::string& argname, Env& env, Signature sig, SourceSpan pstate, Backtraces traces, Context& ctx) { - ExpressionObj exp = ARG(argname, Expression); - if (exp->concrete_type() == Expression::NULL_VAL) { - sass::ostream msg; - msg << argname << ": null is not a valid selector: it must be a string,\n"; - msg << "a list of strings, or a list of lists of strings for `" << function_name(sig) << "'"; - error(msg.str(), exp->pstate(), traces); - } - if (String_Constant* str = Cast(exp)) { - str->quote_mark(0); - } - sass::string exp_src = exp->to_string(ctx.c_options); - ItplFile* source = SASS_MEMORY_NEW(ItplFile, exp_src.c_str(), exp->pstate()); - return Parser::parse_selector(source, ctx, traces, false); - } - - CompoundSelectorObj get_arg_sel(const sass::string& argname, Env& env, Signature sig, SourceSpan pstate, Backtraces traces, Context& ctx) { - ExpressionObj exp = ARG(argname, Expression); - if (exp->concrete_type() == Expression::NULL_VAL) { - sass::ostream msg; - msg << argname << ": null is not a string for `" << function_name(sig) << "'"; - error(msg.str(), exp->pstate(), traces); - } - if (String_Constant* str = Cast(exp)) { - str->quote_mark(0); - } - sass::string exp_src = exp->to_string(ctx.c_options); - ItplFile* source = SASS_MEMORY_NEW(ItplFile, exp_src.c_str(), exp->pstate()); - SelectorListObj sel_list = Parser::parse_selector(source, ctx, traces, false); - if (sel_list->length() == 0) return {}; - return sel_list->first()->first(); - } - - - } - -} diff --git a/src/fn_utils.hpp b/src/fn_utils.hpp index 7f9a354f40..8870f39b8d 100644 --- a/src/fn_utils.hpp +++ b/src/fn_utils.hpp @@ -1,62 +1,123 @@ -#ifndef SASS_FN_UTILS_H -#define SASS_FN_UTILS_H +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#ifndef SASS_FN_UTILS_HPP +#define SASS_FN_UTILS_HPP -// sass.hpp must go before all system headers to get the -// __EXTENSIONS__ fix on Solaris. -#include "sass.hpp" +// sass.hpp must go before all system headers +// to get the __EXTENSIONS__ fix on Solaris. +#include "capi_sass.hpp" -#include "units.hpp" -#include "backtrace.hpp" -#include "environment.hpp" -#include "ast_fwd_decl.hpp" -#include "error_handling.hpp" +// Make some macros available +#include "ast_fwd_decl.hpp" namespace Sass { - #define FN_PROTOTYPE \ - Env& env, \ - Env& d_env, \ - Context& ctx, \ - Signature sig, \ - SourceSpan pstate, \ - Backtraces& traces, \ - SelectorStack selector_stack, \ - SelectorStack original_stack \ - - typedef const char* Signature; - typedef PreValue* (*Native_Function)(FN_PROTOTYPE); - #define BUILT_IN(name) PreValue* name(FN_PROTOTYPE) - - #define ARG(argname, argtype) get_arg(argname, env, sig, pstate, traces) - // special function for weird hsla percent (10px == 10% == 10 != 0.1) - #define ARGVAL(argname) get_arg_val(argname, env, sig, pstate, traces) // double - - Definition* make_native_function(Signature, Native_Function, Context& ctx); - Definition* make_c_function(Sass_Function_Entry c_func, Context& ctx); - - namespace Functions { - - template - T* get_arg(const sass::string& argname, Env& env, Signature sig, SourceSpan pstate, Backtraces traces) - { - T* val = Cast(env[argname]); - if (!val) { - error("argument `" + argname + "` of `" + sig + "` must be a " + T::type_name(), pstate, traces); - } - return val; + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // Returns whether [lhs] and [rhs] are equal within [epsilon]. + inline bool fuzzyEquals(double lhs, double rhs, double epsilon) { + return fabs(lhs - rhs) < epsilon; + } + + // Returns whether [lhs] is less than [rhs], and not [fuzzyEquals]. + inline bool fuzzyLessThan(double lhs, double rhs, double epsilon) { + return lhs < rhs && !fuzzyEquals(lhs, rhs, epsilon); + } + + // Returns whether [lhs] is less than [rhs], or [fuzzyEquals]. + inline bool fuzzyLessThanOrEquals(double lhs, double rhs, double epsilon) { + return lhs < rhs || fuzzyEquals(lhs, rhs, epsilon); + } + + // Returns whether [lhs] is greater than [rhs], and not [fuzzyEquals]. + inline bool fuzzyGreaterThan(double lhs, double rhs, double epsilon) { + return lhs > rhs && !fuzzyEquals(lhs, rhs, epsilon); + } + + // Returns whether [lhs] is greater than [rhs], or [fuzzyEquals]. + inline bool fuzzyGreaterThanOrEquals(double lhs, double rhs, double epsilon) { + return lhs > rhs || fuzzyEquals(lhs, rhs, epsilon); + } + + // Returns whether [number] is [fuzzyEquals] to an integer. + inline bool fuzzyIsInt(double number, double epsilon) { + // Check against 0.5 rather than 0.0 so that we catch numbers that + // are both very slightly above an integer, and very slightly below. + double _fabs_ = fabs(number - 0.5); + double _fmod_ = fmod(_fabs_, 1.0); + return fuzzyEquals(_fmod_, 0.5, epsilon); + } + + // Rounds [number] to the nearest integer. + // This rounds up numbers that are [fuzzyEquals] to `X.5`. + inline long fuzzyRound(double number, double epsilon) { + // If the number is within epsilon of X.5, + // round up (or down for negative numbers). + if (number > 0) { + return lround(fuzzyLessThan( + fmod(number, 1.0), 0.5, epsilon) + ? floor(number) : ceill(number)); } + return lround(fuzzyLessThanOrEquals( + fmod(number, 1.0), -0.5, epsilon) + ? floorl(number) : ceill(number)); + } + + // Returns `true` if it's within [min] and [max], + // or [number] is [fuzzyEquals] to [min] or [max]. + inline bool fuzzyCheckRange(double number, double min, double max, double epsilon) + { + return (number > min && number < max) + || fuzzyEquals(number, min, epsilon) + || fuzzyEquals(number, max, epsilon); + } - Map* get_arg_m(const sass::string& argname, Env& env, Signature sig, SourceSpan pstate, Backtraces traces); // maps only - Number* get_arg_n(const sass::string& argname, Env& env, Signature sig, SourceSpan pstate, Backtraces traces); // numbers only - double alpha_num(const sass::string& argname, Env& env, Signature sig, SourceSpan pstate, Backtraces traces); // colors only - double color_num(const sass::string& argname, Env& env, Signature sig, SourceSpan pstate, Backtraces traces); // colors only - double get_arg_r(const sass::string& argname, Env& env, Signature sig, SourceSpan pstate, Backtraces traces, double lo, double hi); // colors only - double get_arg_val(const sass::string& argname, Env& env, Signature sig, SourceSpan pstate, Backtraces traces); // shared - SelectorListObj get_arg_sels(const sass::string& argname, Env& env, Signature sig, SourceSpan pstate, Backtraces traces, Context& ctx); // selectors only - CompoundSelectorObj get_arg_sel(const sass::string& argname, Env& env, Signature sig, SourceSpan pstate, Backtraces traces, Context& ctx); // selectors only + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + inline double round64(double val, double epsilon) + { + // https://github.com/sass/sass/commit/4e3e1d5684cc29073a507578fc977434ff488c93 + // ToDo: maybe speed up further by using `std::remainder` + double rest = std::fmod(val, 1.0) / 5.0; + if (val >= 0) { + if (0.1 - rest < epsilon) return std::ceil(val); + else return std::floor(val); + } + if (rest + 0.1 <= epsilon) return std::floor(val); + else return std::ceil(val); } -} + template + inline T clamp(const T& n, const T& lower, const T& upper) + { + return std::max(lower, std::min(n, upper)); + } + + template + inline T absmod(const T& n, const T& r) + { + T m = std::fmod(n, r); + if (m < 0.0) m += r; + return m; + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + #define FN_PROTOTYPE \ + const SourceSpan& pstate, \ + const ValueVector& arguments, \ + Compiler& compiler, \ + Eval& eval \ + + #define BUILT_IN_FN(name) Value* name(FN_PROTOTYPE) + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + +}; #endif diff --git a/src/hashing.hpp b/src/hashing.hpp new file mode 100644 index 0000000000..1c33d85c3f --- /dev/null +++ b/src/hashing.hpp @@ -0,0 +1,54 @@ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#ifndef SASS_HASHING_HPP +#define SASS_HASHING_HPP + +// sass.hpp must go before all system headers +// to get the __EXTENSIONS__ fix on Solaris. +#include "capi_sass.hpp" + +#include "randomize.hpp" + +////////////////////////////////////////////////////////// +// `hash_combine` comes from boost (functional/hash): +// http://www.boost.org/doc/libs/1_35_0/doc/html/hash/combine.html +// Boost Software License - Version 1.0 +// http://www.boost.org/users/license.html +////////////////////////////////////////////////////////// + +namespace Sass { + + template + inline void hash_combine(std::size_t& hash, const T& val) + { + hash ^= std::hash()(val) + getHashSeed() + + (hash << 6) + (hash >> 2); + } + // EO hash_combine + + template + inline void hash_start(std::size_t& hash, const T& val) + { + hash = std::hash()(val) + getHashSeed(); + } + // EO hash_start + + // Not sure if calling std::hash has any overhead!? + inline void hash_combine(std::size_t& hash, std::size_t val) + { + hash ^= val + getHashSeed() + + (hash << 6) + (hash >> 2); + } + // EO hash_combine + + // Not sure if calling std::hash has any overhead!? + inline void hash_start(std::size_t& hash, std::size_t val) + { + hash = val + getHashSeed(); + } + // EO hash_start + +} + +#endif diff --git a/src/import.cpp b/src/import.cpp new file mode 100644 index 0000000000..0c02048ec3 --- /dev/null +++ b/src/import.cpp @@ -0,0 +1,82 @@ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#include "import.hpp" + +#include "sources.hpp" +#include "exceptions.hpp" + +namespace Sass { + + // Entry point for top level file import + // Don't load like other includes, we do not + // check inside include paths for this file! + void Import::loadIfNeeded(BackTraces& traces) + { + // Only load once + if (isLoaded()) return; + // Check if entry file-path is given + // Use err string of LoadedImport + if (getAbsPath() == nullptr) { + throw std::runtime_error( + "No file path given to be loaded."); + } + // try to read the content of the resolved file entry + // the memory buffer returned to us must be freed by us! + if (char* contents = File::slurp_file(getAbsPath(), CWD())) { + // Upgrade to a source file + // ToDo: Add sourcemap parsing + source = SASS_MEMORY_NEW(SourceFile, + source->getImpPath(), + source->getAbsPath(), + contents, nullptr + ); + } + else { + // Throw error if read has failed + throw Exception::IoError(traces, + "File not found or unreadable", + File::abs2rel(source->getAbsPath())); + } + } + + const char* Import::getImpPath() const + { + return source->getImpPath(); + } + + const char* Import::getAbsPath() const + { + return source->getAbsPath(); + } + + const char* Import::getFileName() const + { + return source->getFileName(); + } + + const char* Import::getErrorMsg() const + { + return error; + } + + void Import::setErrorMsg(const char* msg) + { + sass_free_c_string(error); + error = sass_copy_c_string(msg); + } + + Import::Import( + SourceData* source, + SassImportSyntax syntax) : + source(source), + syntax(syntax) + {} + + bool Import::isLoaded() const + { + return source && source->content(); + } + +} + diff --git a/src/import.hpp b/src/import.hpp new file mode 100644 index 0000000000..97073f8baf --- /dev/null +++ b/src/import.hpp @@ -0,0 +1,121 @@ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#ifndef SASS_IMPORT_HPP +#define SASS_IMPORT_HPP + +#include "file.hpp" + +namespace Sass { + + // requested import + class ImportRequest { + public: + // requested import path + sass::string imp_path; + // parent context path + sass::string ctx_path; + // base derived from context path + // this really just acts as a cache + sass::string base_path; + // Consider `.import` files? + bool considerImports = false; + public: + ImportRequest( + sass::string imp_path, + sass::string ctx_path, + bool considerImports) : + imp_path(File::make_canonical_path(imp_path)), + ctx_path(File::make_canonical_path(ctx_path)), + base_path(File::dir_name(ctx_path)), + considerImports(considerImports) + { + if (base_path == "stream://") { + base_path = CWD(); + } + } + + bool operator==(const ImportRequest& other) const { + return considerImports == other.considerImports + && imp_path == other.imp_path + && ctx_path == other.ctx_path; + } + + ImportRequest() {}; + }; + + // a resolved include (final import) + class ResolvedImport : public ImportRequest { + public: + // resolved absolute path + sass::string abs_path; + // which importer to use + SassImportSyntax syntax; + public: + ResolvedImport( + const ImportRequest& imp, + sass::string abs_path, + SassImportSyntax syntax) + : ImportRequest(imp), + abs_path(abs_path), + syntax(syntax) + {} + }; + + + // Base class for entry points + class Import : public RefCounted { + public: + SourceDataObj source; + SassImportSyntax syntax; + char* error = nullptr; + void loadIfNeeded(BackTraces& traces); + bool isLoaded() const; + const char* getImpPath() const; + const char* getAbsPath() const; + const char* getFileName() const; + // This is used by custom importer + // Easiest way to communicate back + const char* getErrorMsg() const; + void setErrorMsg(const char* msg); + Import(SassImportSyntax syntax = SASS_IMPORT_AUTO) : + syntax(syntax) + {} + + ~Import() { + sass_free_c_string(error); + } + + Import( + SourceData* source, + SassImportSyntax syntax); + + CAPI_WRAPPER(Import, SassImport); + }; + +} + +namespace std { + template <> struct hash { + public: + inline size_t operator()( + const Sass::ImportRequest& import) const + { + size_t hash = import.considerImports; + Sass::hash_combine(hash, + MurmurHash2( + (void*)import.base_path.c_str(), + (int)import.base_path.size(), + Sass::getHashSeed())); + + Sass::hash_combine(hash, + MurmurHash2( + (void*)import.imp_path.c_str(), + (int)import.imp_path.size(), + Sass::getHashSeed())); + return hash; + } + }; +} + +#endif diff --git a/src/inspect.cpp b/src/inspect.cpp index bdc73cdac3..e982553013 100644 --- a/src/inspect.cpp +++ b/src/inspect.cpp @@ -1,108 +1,318 @@ -// sass.hpp must go before all system headers to get the -// __EXTENSIONS__ fix on Solaris. -#include "sass.hpp" +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#include "inspect.hpp" -#include -#include -#include #include -#include -#include +#include "file.hpp" +#include "ast_css.hpp" +#include "charcode.hpp" +#include "character.hpp" +#include "exceptions.hpp" +#include "ast_values.hpp" +#include "ast_selectors.hpp" +#include "fn_utils.hpp" -#include "ast.hpp" -#include "inspect.hpp" -#include "context.hpp" -#include "listize.hpp" -#include "color_maps.hpp" -#include "utf8/checked.h" +#include "debugger.hpp" namespace Sass { - Inspect::Inspect(const Emitter& emi) - : Emitter(emi) - { } - Inspect::~Inspect() { } + // Import some namespaces + using namespace Charcode; + using namespace Character; - // statements - void Inspect::operator()(Block* block) - { - if (!block->is_root()) { - add_open_mapping(block); - append_scope_opener(); - } - if (output_style() == NESTED) indentation += block->tabs(); - for (size_t i = 0, L = block->length(); i < L; ++i) { - (*block)[i]->perform(this); + sass::string PrintNumber(double nr, const OutputOptions& outopt) { + + // Avoid streams + char buf[255]; + snprintf(buf, 255, + outopt.nr_sprintf, + nr); + + // Operate from behind + char* end = buf; + + // Move to last position + while (*end != 0) ++end; + if (end != buf) end--; + // Delete trailing zeros + while (*end == '0') { + *end = 0; + end--; } - if (output_style() == NESTED) indentation -= block->tabs(); - if (!block->is_root()) { - append_scope_closer(); - add_close_mapping(block); + // Delete trailing decimal separator + if (*end == '.') *end = 0; + + // Some final cosmetics + if (buf[0] == '-' && buf[1] == '0' && buf[2] == 0) { + buf[0] = '0'; buf[1] = 0; } + // add unit now + return sass::string(buf); + } - void Inspect::operator()(StyleRule* ruleset) + Inspect::Inspect(const OutputOptions& outopt) + : Emitter(outopt), quotes(true), inspect(false) { - if (ruleset->selector()) { - ruleset->selector()->perform(this); - } - if (ruleset->block()) { - ruleset->block()->perform(this); - } } - void Inspect::operator()(Keyframe_Rule* rule) + Inspect::Inspect(Logger& logger, const OutputOptions& outopt) + : Emitter(outopt), quotes(true), inspect(false) { - if (rule->name()) rule->name()->perform(this); - if (rule->block()) rule->block()->perform(this); } - void Inspect::operator()(Bubble* bubble) + void Inspect::acceptCssString(const CssString* node) + { + append_token(node->text(), node); + } + + void Inspect::visitBlockStatements(CssNodeVector children) { - append_indentation(); - append_token("::BUBBLE", bubble); append_scope_opener(); - bubble->node()->perform(this); + for (CssNode* stmt : children) { + stmt->accept(this); + } append_scope_closer(); } - void Inspect::operator()(MediaRule* rule) + void Inspect::renderUnquotedString(const sass::string& text) { - append_indentation(); - append_token("@media", rule); - append_mandatory_space(); - if (rule->block()) { - rule->block()->perform(this); + bool afterNewline = false; + for (size_t i = 0; i < text.size(); i++) { + uint8_t chr = text[i]; + switch (chr) { + case $lf: + append_char($space); + afterNewline = true; + break; + + case $space: + if (!afterNewline) { + append_char($space); + } + break; + + default: + append_char(chr); + afterNewline = false; + break; + } + } + } + + template + bool Inspect::_tryPrivateUseCharacter(const octet_iterator& begin, const octet_iterator& end, size_t& offset) { + octet_iterator it = begin + offset; + if (output_style() == SASS_STYLE_COMPRESSED) return false; + // check if char is utf8 character + auto asd = utf8::internal::sequence_length(it); + if (asd > 1) { + uint32_t code_point; + /*auto foo =*/ utf8::internal::validate_next(it, end, code_point); + if (code_point >= 0xE000 && code_point <= 0xF8FF) { + append_char($backslash); + // ToDo: do without sstream + sass::sstream is; + is << std::hex << code_point; + append_string(is.str()); + offset += asd; + return true; + } + } + return false; + } + + void Inspect::renderQuotedString(const sass::string& text, uint8_t quotes) + { + + // Scan the string first, dart-sass seems to do some fancy + // trick by calling itself recursively when it encounters a + // conflicting quote during the output, throwing away buffers. + bool includesSingleQuote = text.find($apos) != sass::string::npos; + bool includesDoubleQuote = text.find($quote) != sass::string::npos; + + // If both quotes are encountered + if (quotes == $nul) { + if (includesSingleQuote) quotes = $quote; + else if (includesDoubleQuote) quotes = $apos; + else quotes = $quote; } + + append_char(quotes); + + uint8_t chr, next; + for (size_t i = 0, iL = text.size(); i < iL; i++) { + chr = text[i]; + switch (chr) { + case $apos: + if (quotes == $apos) { + append_char($backslash); + } + append_char($apos); + break; + case $quote: + if (quotes == $quote) { + append_char($backslash); + } + append_char($quote); + break; + // Write newline characters and unprintable ASCII characters as escapes. + case $nul: + case $soh: + case $stx: + case $etx: + case $eot: + case $enq: + case $ack: + case $bel: + case $bs: + case $lf: + case $vt: + case $ff: + case $cr: + case $so: + case $si: + case $dle: + case $dc1: + case $dc2: + case $dc3: + case $dc4: + case $nak: + case $syn: + case $etb: + case $can: + case $em: + case $sub: + case $esc: + case $fs: + case $gs: + case $rs: + case $us: + case $del: + append_char($backslash); + if (chr > 0xF) append_char(hexCharFor(chr >> 4)); + append_char(hexCharFor(chr & 0xF)); + if (iL == i + 1) break; + next = text[i+1]; + if (isHex(next) || next == $space || next == $tab) { + append_mandatory_space(); + } + break; + case $backslash: + append_char($backslash); + append_char($backslash); + break; + default: + if (_tryPrivateUseCharacter(text.begin(), text.end(), i)) { + + } + else { + append_char(chr); + } + break; + } + } + + append_char(quotes); + } + // EO renderQuotedString - void Inspect::operator()(CssMediaRule* rule) + + void Inspect::visitCssMediaRule(CssMediaRule* node) { - if (output_style() == NESTED) - indentation += rule->tabs(); append_indentation(); - append_token("@media", rule); + append_token("@media", node); append_mandatory_space(); - in_media_block = true; bool joinIt = false; - for (auto query : rule->elements()) { + for (auto query : node->queries()) { if (joinIt) { append_comma_separator(); append_optional_space(); } - operator()(query); + acceptCssMediaQuery(query); joinIt = true; } - if (rule->block()) { - rule->block()->perform(this); + visitBlockStatements(node->elements()); + } + // EO visitCssMediaRule + + void Inspect::visitCssStyleRule(CssStyleRule* node) + { + SelectorListObj s = node->selector(); + + if (!s || s->empty()) return; + if (!node || node->isInvisibleCss()) return; + + // if (output_style() == SASS_STYLE_NESTED) { + // indentation += node->tabs(); + // } + + if (outopt.source_comments) { + sass::sstream ss; + append_indentation(); + sass::string path(File::abs2rel(node->pstate().getAbsPath(), ".", CWD())); // ToDo: optimize + ss << "/* line " << node->pstate().getLine() << ", " << path << " */"; + append_string(ss.str()); + append_optional_linefeed(); } - in_media_block = false; - if (output_style() == NESTED) - indentation -= rule->tabs(); + + // scheduled_crutch = s; + if (s) visitSelectorList(s); + append_scope_opener(node); + + for (size_t i = 0, L = node->size(); i < L; ++i) { + node->get(i)->accept(this); // XX + } + + // if (output_style() == SASS_STYLE_NESTED) { + // indentation -= node->tabs(); + // } + append_scope_closer(node); + } + // EO visitCssStyleRule + + void Inspect::visitCssSupportsRule(CssSupportsRule* rule) + { + if (rule == nullptr) return; + if (rule->isInvisibleCss()) return; + + // if (output_style() == SASS_STYLE_NESTED) { + // indentation += rule->tabs(); + // } + append_indentation(); + append_token("@supports", rule); + append_mandatory_space(); +// if (const auto& cond = rule->condition()) { +// if (const auto& str = cond->isaString()) { +// const auto& text = str->value(); +// if (text.size() > 0 && text[0] == $lparen) { +// // Do not add any space in this case +// } else append_optional_space(); +// } if (cond->isaList()) { +// // Do not add any space in this case +// } +// else append_optional_space(); +// } else + rule->condition()->accept(this); + append_scope_opener(); + + size_t L = rule->size(); + for (size_t i = 0; i < L; ++i) { + rule->get(i)->accept(this); + if (i < L - 1) append_special_linefeed(); + } + + // if (output_style() == SASS_STYLE_NESTED) { + // indentation -= rule->tabs(); + // } + + append_scope_closer(); } - void Inspect::operator()(CssMediaQuery* query) + void Inspect::acceptCssMediaQuery(CssMediaQuery* query) { bool joinIt = false; if (!query->modifier().empty()) { @@ -113,522 +323,655 @@ namespace Sass { append_string(query->type()); joinIt = true; } - for (auto feature : query->features()) { + bool isFirst = true; + for (const sass::string& feature : query->features()) { if (joinIt) { append_mandatory_space(); - append_string("and"); + append_string(query->conjunction() ? "and" : "or"); append_mandatory_space(); } - append_string(feature); + if (isFirst && StringUtils::startsWith(feature, "(not ")) { + append_string(feature.substr(1, feature.size() - 2)); + } + else { + append_string(feature); + } joinIt = true; + isFirst = false; } } - void Inspect::operator()(SupportsRule* feature_block) + void Inspect::visitCssComment(CssComment* c) { - append_indentation(); - append_token("@supports", feature_block); - append_mandatory_space(); - feature_block->condition()->perform(this); - feature_block->block()->perform(this); + bool important = c->isPreserved(); + if (output_style() == SASS_STYLE_COMPRESSED || output_style() == SASS_STYLE_COMPACT) { + if (!important) return; + } + if (output_style() != SASS_STYLE_COMPRESSED || important) { + append_indentation(); + append_string(c->text()); + if (indentation == 0) { + append_mandatory_linefeed(); + } + else { + append_optional_linefeed(); + } + } } + // EO visitCssComment - void Inspect::operator()(AtRootRule* at_root_block) + void Inspect::visitCssDeclaration(CssDeclaration* node) { + RAII_FLAG(in_declaration, true); + RAII_FLAG(in_custom_property, + node->is_custom_property()); + // if (output_style() == SASS_STYLE_NESTED) + // indentation += node->tabs(); append_indentation(); - append_token("@at-root ", at_root_block); - append_mandatory_space(); - if(at_root_block->expression()) at_root_block->expression()->perform(this); - if(at_root_block->block()) at_root_block->block()->perform(this); + if (node->name()) { + force_next_mapping = true; + acceptCssString(node->name()); + force_next_mapping = false; + } + append_colon_separator(); + if (node->value()) { + force_next_mapping = true; + node->value()->accept(this); + force_next_mapping = false; + } + append_delimiter(); + // if (output_style() == SASS_STYLE_NESTED) + // indentation -= node->tabs(); } + // EO visitCssDeclaration - void Inspect::operator()(AtRule* at_rule) - { - append_indentation(); - append_token(at_rule->keyword(), at_rule); - if (at_rule->selector()) { - append_mandatory_space(); - bool was_wrapped = in_wrapped; - in_wrapped = true; - at_rule->selector()->perform(this); - in_wrapped = was_wrapped; - } - if (at_rule->value()) { - append_mandatory_space(); - at_rule->value()->perform(this); - } - if (at_rule->block()) { - at_rule->block()->perform(this); + bool Inspect::_IsInvisible(CssNode* node) { + if (inspect) return false; + if (output_style() == SASS_STYLE_COMPRESSED) { + return node->isInvisibleHidingComments(); } else { - append_delimiter(); + return node->isInvisible(); } } - void Inspect::operator()(Declaration* dec) + // statements // visitCssStylesheet + void Inspect::visitCssRoot(CssRoot* block) { - if (dec->value()->concrete_type() == Expression::NULL_VAL) return; - bool was_decl = in_declaration; - in_declaration = true; - LOCAL_FLAG(in_custom_property, dec->is_custom_property()); - if (output_style() == NESTED) - indentation += dec->tabs(); - append_indentation(); - if (dec->property()) - dec->property()->perform(this); - append_colon_separator(); - - if (dec->value()->concrete_type() == Expression::SELECTOR) { - ExpressionObj ls = Listize::perform(dec->value()); - ls->perform(this); - } else { - dec->value()->perform(this); + for (size_t i = 0, L = block->size(); i < L; ++i) { + auto& child = block->get(i); + if (_IsInvisible(block)) continue; + child->accept(this); // XX } - if (dec->is_important()) { - append_optional_space(); - append_string("!important"); - } - append_delimiter(); - if (output_style() == NESTED) - indentation -= dec->tabs(); - in_declaration = was_decl; } - void Inspect::operator()(Assignment* assn) + void Inspect::visitCssKeyframeBlock(CssKeyframeBlock* node) { - append_token(assn->variable(), assn); - append_colon_separator(); - assn->value()->perform(this); - if (assn->is_default()) { - append_optional_space(); - append_string("!default"); - } - append_delimiter(); - } + if (node->selector()) { - void Inspect::operator()(Import* import) - { - if (!import->urls().empty()) { - append_token("@import", import); - append_mandatory_space(); + const sass::vector& selector + = node->selector()->texts(); - import->urls().front()->perform(this); - if (import->urls().size() == 1) { - if (import->import_queries()) { - append_mandatory_space(); - import->import_queries()->perform(this); + if (!selector.empty()) { + append_indentation(); + bool addComma = false; + for (sass::string sel : selector) { + if (addComma) { + append_comma_separator(); + } + append_string(sel); + addComma = true; } } - append_delimiter(); - for (size_t i = 1, S = import->urls().size(); i < S; ++i) { - append_mandatory_linefeed(); - append_token("@import", import); - append_mandatory_space(); - import->urls()[i]->perform(this); - if (import->urls().size() - 1 == i) { - if (import->import_queries()) { - append_mandatory_space(); - import->import_queries()->perform(this); - } - } - append_delimiter(); + } + // StringLiteralObj v2 = node->name2(); + // + // if (!v2.isNull()) { + // append_indentation(); + // v2->accept(this); + // } + // + // append_scope_opener(); + // for (size_t i = 0, L = r->size(); i < L; ++i) { + // Statement_Obj stm = r->get(i); + // stm->accept(this); + // if (i < L - 1) append_special_linefeed(); + // } + // append_scope_closer(); + if (!node->isInvisibleCss()) { + append_scope_opener(); + for (CssNode* child : node->elements()) { + child->accept(this); } + append_scope_closer(); } - } + } - void Inspect::operator()(Import_Stub* import) + void Inspect::visitCssAtRule(CssAtRule* node) { append_indentation(); - append_token("@import", import); - append_mandatory_space(); - append_string(import->imp_path()); - append_delimiter(); + if (node->name()) { + append_char($at); + acceptCssString(node->name()); + } + if (node->value()) { + append_mandatory_space(); + acceptCssString(node->value()); + } + if (node->isChildless()) { + append_delimiter(); + } + else { + visitBlockStatements(node->elements()); + } } - void Inspect::operator()(WarningRule* warning) + void Inspect::visitCssImport(CssImport* import) { append_indentation(); - append_token("@warn", warning); + add_open_mapping(import, true); + append_string("@import"); append_mandatory_space(); - warning->message()->perform(this); + CssString* url(import->url()); + append_token(url->text(), url); + if (import->modifiers()) { + append_mandatory_space(); + CssString* text(import->modifiers()); + append_token(text->text(), text); + } + // if (!import->media().empty()) { + // bool first = true; + // append_mandatory_space(); + // for (CssMediaQueryObj query : import->media()) { + // if (first == false) { + // append_comma_separator(); + // append_optional_space(); + // } + // acceptCssMediaQuery(query); + // first = false; + // } + // } + add_close_mapping(import, true); append_delimiter(); } - void Inspect::operator()(ErrorRule* error) + void Inspect::_writeMapElement(Interpolant* itpl) { - append_indentation(); - append_token("@error", error); - append_mandatory_space(); - error->message()->perform(this); - append_delimiter(); + if (Value * value = itpl->isaValue()) { + bool needsParens = false; + if (List * list = value->isaList()) { + needsParens = list->separator() == SASS_COMMA; + if (list->hasBrackets()) needsParens = false; + } + if (needsParens) { + append_char($lparen); + value->accept(this); + append_char($rparen); + } + else { + value->accept(this); + } + } + else if (ItplString* str = itpl->isaItplString()) { + append_token(str->text(), str); + } + else { + throw std::runtime_error("Expression not evaluated"); + } } - void Inspect::operator()(DebugRule* debug) + void Inspect::acceptNameSpaceSelector(SelectorNS* selector) { - append_indentation(); - append_token("@debug", debug); - append_mandatory_space(); - debug->value()->perform(this); - append_delimiter(); + flush_schedules(); + if (selector->hasNs()) { + write_string(selector->ns()); + write_char($pipe); + } + write_string(selector->name()); } - void Inspect::operator()(Comment* comment) + void Inspect::visitAttributeSelector(AttributeSelector* attribute) { - in_comment = true; - comment->text()->perform(this); - in_comment = false; + append_string("["); + acceptNameSpaceSelector(attribute); + if (!attribute->op().empty()) { + append_string(attribute->op()); + if (attribute->isIdentifier() && !StringUtils::startsWith(attribute->value(), "--", 2)) { + append_string(attribute->value()); + if (attribute->modifier() != 0) { + append_optional_space(); + } + } + else { + renderQuotedString(attribute->value()); + if (attribute->modifier() != 0) { + append_optional_space(); + } + } + } + // add_close_mapping(attribute); + if (attribute->modifier() != 0) { + append_mandatory_space(); + append_char(attribute->modifier()); + } + append_string("]"); } - void Inspect::operator()(If* cond) + void Inspect::visitClassSelector(ClassSelector* klass) { - append_indentation(); - append_token("@if", cond); - append_mandatory_space(); - cond->predicate()->perform(this); - cond->block()->perform(this); - if (cond->alternative()) { - append_optional_linefeed(); - append_indentation(); - append_string("else"); - cond->alternative()->perform(this); - } + // Skip over '.' character + move_next_mapping(1, 1); + add_open_mapping(klass, true); + append_string(klass->name()); + add_close_mapping(klass, true); } - void Inspect::operator()(ForRule* loop) + void Inspect::visitComplexSelector(ComplexSelector* complex) { - append_indentation(); - append_token("@for", loop); - append_mandatory_space(); - append_string(loop->variable()); - append_string(" from "); - loop->lower_bound()->perform(this); - append_string(loop->is_inclusive() ? " through " : " to "); - loop->upper_bound()->perform(this); - loop->block()->perform(this); + bool many = false; + + // debug_ast(complex, "visit: "); + + // schedule_mapping(complex->last()); + + for (SelectorCombinator* combinator : complex->leadingCombinators()) { + visitSelectorCombinator(combinator); + append_mandatory_space(); + } + + for (const CplxSelComponentObj& item : complex->elements()) { + if (many) append_mandatory_space(); + visitSelectorComponent(item); + many = true; + } + + schedule_mapping(nullptr); + } - void Inspect::operator()(EachRule* loop) + void Inspect::visitSelectorComponent(CplxSelComponent* comp) { - append_indentation(); - append_token("@each", loop); - append_mandatory_space(); - append_string(loop->variables()[0]); - for (size_t i = 1, L = loop->variables().size(); i < L; ++i) { - append_comma_separator(); - append_string(loop->variables()[i]); - } - append_string(" in "); - loop->list()->perform(this); - loop->block()->perform(this); + if (CompoundSelector* compound = comp->selector()) { + visitCompoundSelector(compound); + } + for (SelectorCombinator* combinator : comp->combinators()) { + visitSelectorCombinator(combinator); + } } - void Inspect::operator()(WhileRule* loop) + void Inspect::visitCompoundSelector(CompoundSelector* compound) { - append_indentation(); - append_token("@while", loop); - append_mandatory_space(); - loop->predicate()->perform(this); - loop->block()->perform(this); + + size_t position = wbuf.buffer.size(); + + if (compound->withExplicitParent()) { + if (inspect == true) { + append_string("&"); + } + } + + for (const SimpleSelectorObj& item : compound->elements()) { + item->accept(this); + } + + // If we emit an empty compound, it's because all of the + // components got optimized out because they match all + // selectors, so we just emit the universal selector. + if (position == wbuf.buffer.size()) { + write_char($asterisk); + } + + // Add the post line break (from ruby sass) + // Dart sass uses another logic for newlines + if (compound->hasPostLineBreak()) { + if (output_style() != SASS_STYLE_COMPACT) { + append_optional_linefeed(); + } + } } - void Inspect::operator()(Return* ret) + void Inspect::visitSelectorCombinator(SelectorCombinator* combinator) { - append_indentation(); - append_token("@return", ret); - append_mandatory_space(); - ret->value()->perform(this); - append_delimiter(); + append_optional_space(); + switch (combinator->combinator()) { + case SelectorPrefix::CHILD: append_string(">"); break; + case SelectorPrefix::SIBLING: append_string("+"); break; + case SelectorPrefix::FOLLOWING: append_string("~"); break; + } + append_optional_space(); + // // Add the post line break (from ruby sass) + // // Dart sass uses another logic for newlines + // // if (combinator->hasPostLineBreak()) { + // // if (output_style() != COMPACT) { + // // // append_optional_linefeed(); + // // } + // // } } - void Inspect::operator()(ExtendRule* extend) + void Inspect::visitIDSelector(IDSelector* id) { - append_indentation(); - append_token("@extend", extend); - append_mandatory_space(); - extend->selector()->perform(this); - append_delimiter(); + // Skip over '#' character + move_next_mapping(1, 1); + append_token(id->name(), id); } - void Inspect::operator()(Definition* def) + void Inspect::visitPlaceholderSelector(PlaceholderSelector* placeholder) { - append_indentation(); - if (def->type() == Definition::MIXIN) { - append_token("@mixin", def); - append_mandatory_space(); - } else { - append_token("@function", def); - append_mandatory_space(); - } - append_string(def->name()); - def->parameters()->perform(this); - def->block()->perform(this); + append_token(placeholder->name(), placeholder); } - void Inspect::operator()(Mixin_Call* call) + void Inspect::visitPseudoSelector(PseudoSelector* pseudo) { - append_indentation(); - append_token("@include", call); - append_mandatory_space(); - append_string(call->name()); - if (call->arguments()) { - call->arguments()->perform(this); - } - if (call->block()) { - append_optional_space(); - call->block()->perform(this); + + if (auto sel = pseudo->selector()) { + if (pseudo->name() == "not") { + if (sel->empty()) { + return; + } + } } - if (!call->block()) append_delimiter(); - } - void Inspect::operator()(Content* content) - { - append_indentation(); - append_token("@content", content); - append_delimiter(); + if (pseudo->name() != "") { + append_string(":"); + if (pseudo->isSyntacticElement()) { + append_string(":"); + } + append_token(pseudo->name(), pseudo); + // this whole logic can be done simpler!? copy object? + if (pseudo->selector() || !pseudo->argument().empty()) { + append_string("("); + parentheses_opened = true; + append_string(pseudo->argument()); + if (pseudo->selector() && !pseudo->argument().empty()) { + if (!pseudo->selector()->empty() && + !pseudo->argument().empty()) { + append_mandatory_space(); + } + } + bool was_comma_array = in_comma_array; + in_comma_array = false; + if (pseudo->selector()) { + visitSelectorList(pseudo->selector()); + } + in_comma_array = was_comma_array; + append_string(")"); + } + } } - void Inspect::operator()(Map* map) + void Inspect::visitSelectorList(SelectorList* list) { - if (output_style() == TO_SASS && map->empty()) { - append_string("()"); + if (list->empty()) { return; } - if (map->empty()) return; - if (map->is_invisible()) return; - bool items_output = false; - append_string("("); - for (auto key : map->keys()) { - if (items_output) append_comma_separator(); - key->perform(this); - append_colon_separator(); - LOCAL_FLAG(in_space_array, true); - LOCAL_FLAG(in_comma_array, true); - map->at(key)->perform(this); - items_output = true; + + bool was_comma_array = in_comma_array; + // probably ruby sass equivalent of element_needs_parens + if (!in_declaration && in_comma_array) { + append_string("("); + parentheses_opened = true; + } + + if (in_declaration) in_comma_array = true; + bool first = true; + for (size_t i = 0, L = list->size(); i < L; ++i) { + + + if (list->get(i) == nullptr) continue; + if (!inspect && list->get(i)->isInvisible()) continue; + if (first) append_indentation(); + else { + scheduled_space = 0; + append_comma_separator(); + } + first = false; + if (list->get(i)->hasPreLineFeed()) { + append_optional_linefeed(); + if (output_style() != SASS_STYLE_COMPACT) + append_indentation(); + } + visitComplexSelector(list->get(i)); + } + + in_comma_array = was_comma_array; + // probably ruby sass equivalent of element_needs_parens + if (!in_declaration && in_comma_array) { + append_string(")"); } - append_string(")"); } - sass::string Inspect::lbracket(List* list) { - return list->is_bracketed() ? "[" : "("; + void Inspect::visitTypeSelector(TypeSelector* type) + { + add_open_mapping(type, true); + acceptNameSpaceSelector(type); + add_close_mapping(type, true); } - sass::string Inspect::rbracket(List* list) { - return list->is_bracketed() ? "]" : ")"; + // Returns whether [value] needs parentheses as an + // element in a list with the given [separator]. + bool _elementNeedsParens(SassSeparator separator, const Value* value) { + if (const List * list = value->isaList()) { + if (list->size() < 2) return false; + if (list->hasBrackets()) return false; + switch (separator) { + case SASS_COMMA: + return list->separator() == SASS_COMMA; + case SASS_DIV: + return list->separator() == SASS_COMMA || + list->separator() == SASS_DIV; + default: + return list->separator() != SASS_UNDEF; + } + } + return false; } - void Inspect::operator()(List* list) + sass::string _separatorString(SassSeparator separator, bool compressed) { + switch (separator) { + case SASS_SPACE: + return " "; + case SASS_COMMA: + return compressed ? "," : ", "; + case SASS_DIV: + return compressed ? "/" : " / "; + default: + return ""; + } + } + + void Inspect::visitList(List* list) { - if (list->empty() && (output_style() == TO_SASS || list->is_bracketed())) { - append_string(lbracket(list)); - append_string(rbracket(list)); + // Handle empty case + if (list->empty()) { + if (list->hasBrackets()) { + append_char($lbracket); + append_char($rbracket); + } + else { + append_char($lparen); + append_char($rparen); + } return; } - sass::string sep(list->separator() == SASS_SPACE ? " " : ","); - if ((output_style() != COMPRESSED) && sep == ",") sep += " "; - else if (in_media_block && sep != " ") sep += " "; // verified - if (list->empty()) return; - bool items_output = false; - bool was_space_array = in_space_array; - bool was_comma_array = in_comma_array; - // if the list is bracketed, always include the left bracket - if (list->is_bracketed()) { - append_string(lbracket(list)); - } - // probably ruby sass equivalent of element_needs_parens - else if (output_style() == TO_SASS && - list->length() == 1 && - !list->from_selector() && - !Cast(list->at(0)) && - !Cast(list->at(0)) - ) { - append_string(lbracket(list)); + bool preserveComma = inspect && + list->size() == 1 && + (list->separator() == SASS_COMMA || + list->separator() == SASS_DIV); + + if (list->hasBrackets()) { + append_char($lbracket); } - else if (!in_declaration && (list->separator() == SASS_HASH || - (list->separator() == SASS_SPACE && in_space_array) || - (list->separator() == SASS_COMMA && in_comma_array) - )) { - append_string(lbracket(list)); + else if (preserveComma) { + append_char($lparen); } - if (list->separator() == SASS_SPACE) in_space_array = true; - else if (list->separator() == SASS_COMMA) in_comma_array = true; + const sass::vector& values(list->elements()); - for (size_t i = 0, L = list->size(); i < L; ++i) { - if (list->separator() == SASS_HASH) - { sep[0] = i % 2 ? ':' : ','; } - ExpressionObj list_item = list->at(i); - if (output_style() != TO_SASS) { - if (list_item == nullptr) continue; - if (list_item->is_invisible()) { - // this fixes an issue with "" in a list - if (!Cast(list_item)) { - continue; - } + bool first = true; + sass::string joiner = _separatorString(list->separator(), + output_style() == SASS_STYLE_COMPRESSED); + + for (Value* value : values) { + // Only print `null` when inspecting + if (!inspect && value->isBlank()) continue; + if (first == false) { + append_string(joiner); + } + else { + first = false; + } + if (inspect) { + bool needsParens = _elementNeedsParens( + list->separator(), value); + if (needsParens) { + append_char($lparen); + } + value->accept(this); + if (needsParens) { + append_char($rparen); } } - if (items_output) { - append_string(sep); + else { + value->accept(this); } - if (items_output && sep != " ") - append_optional_space(); - list_item->perform(this); - items_output = true; } - in_comma_array = was_comma_array; - in_space_array = was_space_array; - - // if the list is bracketed, always include the right bracket - if (list->is_bracketed()) { - if (list->separator() == SASS_COMMA && list->size() == 1) { - append_string(","); + if (preserveComma) { + if (list->separator() == SASS_DIV) { + append_char($slash); + } + else { + append_char($comma); + } + if (!list->hasBrackets()) { + append_char($rparen); } - append_string(rbracket(list)); - } - // probably ruby sass equivalent of element_needs_parens - else if (output_style() == TO_SASS && - list->length() == 1 && - !list->from_selector() && - !Cast(list->at(0)) && - !Cast(list->at(0)) - ) { - append_string(","); - append_string(rbracket(list)); - } - else if (!in_declaration && (list->separator() == SASS_HASH || - (list->separator() == SASS_SPACE && in_space_array) || - (list->separator() == SASS_COMMA && in_comma_array) - )) { - append_string(rbracket(list)); } + if (list->hasBrackets()) { + append_char($rbracket); + } } - void Inspect::operator()(Binary_Expression* expr) + void Inspect::acceptInterpolation(Interpolation* node) { - expr->left()->perform(this); - if ( in_media_block || - (output_style() == INSPECT) || ( - expr->op().ws_before - && (!expr->is_interpolant()) - && (expr->is_left_interpolant() || - expr->is_right_interpolant()) - - )) append_string(" "); - switch (expr->optype()) { - case Sass_OP::AND: append_string("&&"); break; - case Sass_OP::OR: append_string("||"); break; - case Sass_OP::EQ: append_string("=="); break; - case Sass_OP::NEQ: append_string("!="); break; - case Sass_OP::GT: append_string(">"); break; - case Sass_OP::GTE: append_string(">="); break; - case Sass_OP::LT: append_string("<"); break; - case Sass_OP::LTE: append_string("<="); break; - case Sass_OP::ADD: append_string("+"); break; - case Sass_OP::SUB: append_string("-"); break; - case Sass_OP::MUL: append_string("*"); break; - case Sass_OP::DIV: append_string("/"); break; - case Sass_OP::MOD: append_string("%"); break; - default: break; // shouldn't get here - } - if ( in_media_block || - (output_style() == INSPECT) || ( - expr->op().ws_after - && (!expr->is_interpolant()) - && (expr->is_left_interpolant() || - expr->is_right_interpolant()) - )) append_string(" "); - expr->right()->perform(this); + // throw std::runtime_error("Interpolation"); + for (Interpolant* itpl : node->elements()) { + if (ItplString* str = itpl->isaItplString()) { + append_token(str->text(), str); + } + else if (Value* value = itpl->isaValue()) { + value->accept(this); + } + else { + throw std::runtime_error("Expression not evaluated"); + } + } } - void Inspect::operator()(Unary_Expression* expr) + void Inspect::visitFunction(Function* value) { - if (expr->optype() == Unary_Expression::PLUS) append_string("+"); - else if (expr->optype() == Unary_Expression::SLASH) append_string("/"); - else append_string("-"); - expr->operand()->perform(this); + append_token("get-function", value); + append_string("("); + parentheses_opened = true; + Callable* fn = value->callable(); + // Function names are safe to quote! + append_token("\""+ fn->name() + "\"", fn); + append_string(")"); } - void Inspect::operator()(Function_Call* call) + + // T visitBoolean(Boolean value); + void Inspect::visitBoolean(Boolean* value) { - append_token(call->name(), call); - call->arguments()->perform(this); + // output the final token + append_token(value->value() ? "true" : "false", value); } - void Inspect::operator()(Variable* var) + bool is_hex_doublet(double n) { - append_token(var->name(), var); + return n == 0x00 || n == 0x11 || n == 0x22 || n == 0x33 || + n == 0x44 || n == 0x55 || n == 0x66 || n == 0x77 || + n == 0x88 || n == 0x99 || n == 0xAA || n == 0xBB || + n == 0xCC || n == 0xDD || n == 0xEE || n == 0xFF; } - void Inspect::operator()(Number* n) + bool is_color_doublet(double r, double g, double b) { + return is_hex_doublet(r) && is_hex_doublet(g) && is_hex_doublet(b); + } - // reduce units - n->reduce(); - - sass::ostream ss; - ss.precision(opt.precision); - ss << std::fixed << n->value(); - - sass::string res = ss.str(); - size_t s = res.length(); - - // delete trailing zeros - for(s = s - 1; s > 0; --s) - { - if(res[s] == '0') { - res.erase(s, 1); + // T visitColorRGBA(SassColor value); + void Inspect::visitColor(Color* color) + { + // output the final token + sass::sstream ss; + + if (color->parsed() && !color->isaColorHwba()) { //&& color->a() < 1 + + if (color->disp().empty()) { + double epsilon = std::pow(0.1, outopt.precision); + if (ColorHsla* hsla = color->isaColorHsla()) { + if (hsla->a() >= 1) { + ss << "hsl("; + ss << PrintNumber(hsla->h(), outopt) << ", "; + ss << PrintNumber(hsla->s(), outopt) << "%, "; + ss << PrintNumber(hsla->l(), outopt) << "%)"; + } + else { + ss << "hsla("; + ss << PrintNumber(hsla->h(), outopt) << ", "; + ss << PrintNumber(hsla->s(), outopt) << "%, "; + ss << PrintNumber(hsla->l(), outopt) << "%, "; + ss << PrintNumber(clamp(hsla->a(), 0, 1), outopt) << ")"; + } } - 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"; - else if (res == "-0") res = "0"; - else if (res == "-0.0") res = "0"; - else if (opt.output_style == COMPRESSED) - { - if (n->zero()) { - // check if handling negative nr - size_t off = res[0] == '-' ? 1 : 0; - // remove leading zero from floating point in compressed mode - if (res[off] == '0' && res[off+1] == '.') res.erase(off, 1); + else if (ColorRgba* rgba = color->isaColorRgba()) { + if (rgba->a() >= 1) { + ss << "rgb("; + ss << PrintNumber(rgba->r(), outopt) << ", "; + ss << PrintNumber(rgba->g(), outopt) << ", "; + ss << PrintNumber(rgba->b(), outopt) << ")"; + } + else { + ss << "rgba("; + ss << PrintNumber(rgba->r(), outopt) << ", "; + ss << PrintNumber(rgba->g(), outopt) << ", "; + ss << PrintNumber(rgba->b(), outopt) << ", "; + ss << PrintNumber(clamp(rgba->a(), 0, 1), outopt) << ")"; + } + } + //else if (ColorHwba* hwba = color->isaColorHwba()) { + // auto rgba = hwba->toRGBA(); + // if (rgba->a() >= 1) { + // ss << "hwb("; + // ss << round64(rgba->r(), epsilon) << ", "; + // ss << round64(rgba->g(), epsilon) << ", "; + // ss << round64(rgba->b(), epsilon) << ")"; + // } + // else { + // ss << "hwba("; + // ss << round64(rgba->r(), epsilon) << ", "; + // ss << round64(rgba->g(), epsilon) << ", "; + // ss << round64(rgba->b(), epsilon) << ", "; + // ss << clamp(rgba->a(), 0, 1) << ")"; + // } + //} + append_token(ss.str(), color); + // append_token(color->toString(), color); } - } - - // add unit now - res += n->unit(); + else { + append_token(color->disp(), color); + } + return; - 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); - } - - // helper function for serializing colors - template - static double cap_channel(double c) { - if (c > range) return range; - else if (c < 0) return 0; - else return c; - } - - void Inspect::operator()(Color_RGBA* c) - { - // output the final token - sass::ostream ss; + ColorRgbaObj c = color->toRGBA(); // original color name // maybe an unknown token @@ -637,58 +980,59 @@ namespace Sass { // resolved color sass::string res_name = name; - double r = Sass::round(cap_channel<0xff>(c->r()), opt.precision); - double g = Sass::round(cap_channel<0xff>(c->g()), opt.precision); - double b = Sass::round(cap_channel<0xff>(c->b()), opt.precision); - double a = cap_channel<1> (c->a()); + double epsilon = std::pow(0.1, outopt.precision); + double r = round64(clamp(c->r(), 0.0, 255.0), epsilon); + double g = round64(clamp(c->g(), 0.0, 255.0), epsilon); + double b = round64(clamp(c->b(), 0.0, 255.0), epsilon); + double a = clamp(c->a(), 0, 1); // get color from given name (if one was given at all) if (name != "" && name_to_color(name)) { - const Color_RGBA* n = name_to_color(name); - r = Sass::round(cap_channel<0xff>(n->r()), opt.precision); - g = Sass::round(cap_channel<0xff>(n->g()), opt.precision); - b = Sass::round(cap_channel<0xff>(n->b()), opt.precision); - a = cap_channel<1> (n->a()); + const ColorRgba* n = name_to_color(name); + r = round64(clamp(n->r(), 0.0, 255.0), epsilon); + g = round64(clamp(n->g(), 0.0, 255.0), epsilon); + b = round64(clamp(n->b(), 0.0, 255.0), epsilon); + a = clamp(n->a(), 0.0, 1.0); } // otherwise get the possible resolved color name else { double numval = r * 0x10000 + g * 0x100 + b; - if (color_to_name(numval)) - res_name = color_to_name(numval); + if (color_to_name((int)numval)) + res_name = color_to_name((int)numval); } - sass::ostream hexlet; + sass::sstream hexlet; // dart sass compressed all colors in regular css always // ruby sass and libsass does it only when not delayed // since color math is going to be removed, this can go too - bool compressed = opt.output_style == COMPRESSED; + bool compressed = outopt.output_style == SASS_STYLE_COMPRESSED; hexlet << '#' << std::setw(1) << std::setfill('0'); // create a short color hexlet if there is any need for it - if (compressed && is_color_doublet(r, g, b) && a == 1) { + if (compressed && is_color_doublet(r, g, b) && a >= 1.0) { hexlet << std::hex << std::setw(1) << (static_cast(r) >> 4); hexlet << std::hex << std::setw(1) << (static_cast(g) >> 4); hexlet << std::hex << std::setw(1) << (static_cast(b) >> 4); - } else { + if (a != 1) hexlet << std::hex << std::setw(1) << (static_cast(a * 255) >> 4); + } + else { hexlet << std::hex << std::setw(2) << static_cast(r); hexlet << std::hex << std::setw(2) << static_cast(g); hexlet << std::hex << std::setw(2) << static_cast(b); + if (a != 1) hexlet << std::hex << std::setw(2) << (static_cast(a * 255) >> 4); } - if (compressed && !c->is_delayed()) name = ""; - if (opt.output_style == INSPECT && a >= 1) { - append_token(hexlet.str(), c); - return; - } + if (compressed) name = ""; // retain the originally specified color definition if unchanged if (name != "") { ss << name; } - else if (a >= 1) { + else if (a >= 1.0) { if (res_name != "") { if (compressed && hexlet.str().size() < res_name.size()) { ss << hexlet.str(); - } else { + } + else { ss << res_name; } } @@ -696,430 +1040,369 @@ namespace Sass { ss << hexlet.str(); } } + else { ss << "rgba("; - ss << static_cast(r) << ","; + ss << PrintNumber(r, outopt) << ","; if (!compressed) ss << " "; - ss << static_cast(g) << ","; + ss << PrintNumber(g, outopt) << ","; if (!compressed) ss << " "; - ss << static_cast(b) << ","; + ss << PrintNumber(b, outopt) << ","; if (!compressed) ss << " "; - ss << a << ')'; + ss << PrintNumber(clamp(a, 0, 1), outopt) << ')'; } append_token(ss.str(), c); - } + // EO visitColorRGBA - void Inspect::operator()(Color_HSLA* c) + // T visitFunction(Function value); + + // T visitMap(Map value); + void Inspect::visitMap(Map* value) { - Color_RGBA_Obj rgba = c->toRGBA(); - operator()(rgba); + if (value->empty()) { + append_string("()"); + return; + } + bool items_output = false; + append_string("("); + parentheses_opened = true; + #if SassSortMapKeysOnOutput + auto keys = value->keys(); + std::sort(keys.begin(), keys.end(), + [](const ValueObj& a, const ValueObj& b) { + return a->toString() < b->toString(); + }); + for (auto key : keys) { + const auto& kv = *value->find(key); + #else + for (const auto& kv : value->elements()) { + #endif + if (items_output) append_comma_separator(); + _writeMapElement(kv.first); + append_colon_separator(); + _writeMapElement(kv.second); + items_output = true; + } + append_string(")"); } - void Inspect::operator()(Boolean* b) + void Inspect::visitNull(Null* value) { + if (output_style() == SASS_STYLE_TO_CSS) return; // output the final token - append_token(b->value() ? "true" : "false", b); + append_token("null", value); } - void Inspect::operator()(String_Schema* ss) + void Inspect::visitCalculation(Calculation* value) { - // Evaluation should turn these into String_Constants, - // so this method is only for inspection purposes. - for (size_t i = 0, L = ss->length(); i < L; ++i) { - if ((*ss)[i]->is_interpolant()) append_string("#{"); - (*ss)[i]->perform(this); - if ((*ss)[i]->is_interpolant()) append_string("}"); + // if (output_style() == SASS_STYLE_TO_CSS) return; + append_string(value->name()); + append_string("("); + parentheses_opened = true; + bool first = true; + for (auto node : value->arguments()) { + if (node == nullptr) continue; + if (!first) { + append_comma_separator(); + append_optional_space(); + } + _writeCalculationValue(node, false); + first = false; } + append_string(")"); } - void Inspect::operator()(String_Constant* s) + void Inspect::_writeCalculationUnits(Units* units) { - append_token(s->value(), s); + append_string(units->unit()); } - void Inspect::operator()(String_Quoted* s) - { - if (const char q = s->quote_mark()) { - append_token(quote(s->value(), q), s); - } else { - append_token(s->value(), s); + static int OpPrecedence(SassOperator op) { + switch (op) { + case ADD: return 1; + case SUB: return 1; + case MUL: return 2; + case DIV: return 2; + default: return 0; } } - void Inspect::operator()(Custom_Error* e) + static bool _parenthesizeCalculationRhs( + SassOperator outer, SassOperator right) { - append_token(e->message(), e); + if (outer == DIV) return true; + if (outer == ADD) return false; + return right == ADD || right == SUB; + } - void Inspect::operator()(Custom_Warning* w) + static bool _parenthesizeCalculationRhsDiv(AstNode* rhs) { - append_token(w->message(), w); + if (auto nr = dynamic_cast(rhs)) { + return std::isfinite(nr->value()) + ? nr->isValidCssUnit() == false + : nr->hasUnits(); + } + return false; } - void Inspect::operator()(SupportsOperation* so) + void Inspect::_writeCalculationValue(AstNode* node, bool wrap) { + if (auto nr = dynamic_cast(node)) { + + if (output_style() == SASS_STYLE_TO_CSS) { + if (!nr->isValidCssUnit()) { + throw Exception::SassScriptException( + "isn't a valid CSS value.", + {}, node->pstate() + ); + } + } + // ToDo: implement cssize + //if (nr->isValidCssUnit() == false) { + // throw std::runtime_error( + // "isn't a valid CSS value"); + //} + + if (std::isnan(nr->value())) { + // if (wrap) append_string("calc("); + if (nr->value() < 0) { + append_string("-NaN"); + } + else { + append_string("NaN"); + } + if (nr->hasUnits()) { + append_string(" * 1"); + _writeCalculationUnits(nr); + } + // if (wrap) append_string(")"); + //_writeCalculationUnits(nr); + return; + } - if (so->needs_parens(so->left())) append_string("("); - so->left()->perform(this); - if (so->needs_parens(so->left())) append_string(")"); + if (std::isinf(nr->value())) { + // if (wrap) append_string("calc("); + if (nr->value() < 0) { + append_string("-infinity"); + } + else { + append_string("infinity"); + } + if (nr->hasUnits()) { + append_string(" * 1"); + _writeCalculationUnits(nr); + } + // if (wrap) append_string(")"); + //_writeCalculationUnits(nr); + return; + } + + if (nr->isValidCssUnit()) { + nr->accept(this); + } + else { + nr->accept(this); + } - if (so->operand() == SupportsOperation::AND) { - append_mandatory_space(); - append_token("and", so); - append_mandatory_space(); - } else if (so->operand() == SupportsOperation::OR) { - append_mandatory_space(); - append_token("or", so); - append_mandatory_space(); } + else if (auto op = dynamic_cast(node)) { + auto lcalc = dynamic_cast(op->left().ptr()); + auto rcalc = dynamic_cast(op->right().ptr()); + bool pl = lcalc != nullptr && OpPrecedence(lcalc->op()) < OpPrecedence(op->op()); - if (so->needs_parens(so->right())) append_string("("); - so->right()->perform(this); - if (so->needs_parens(so->right())) append_string(")"); - } + if (pl) append_string("("); + parentheses_opened = pl; + _writeCalculationValue(op->left()); + if (pl) append_string(")"); - void Inspect::operator()(SupportsNegation* sn) - { - append_token("not", sn); - append_mandatory_space(); - if (sn->needs_parens(sn->condition())) append_string("("); - sn->condition()->perform(this); - if (sn->needs_parens(sn->condition())) append_string(")"); - } + // ToDo: compress white-space + append_optional_space(); + switch (op->op()) { + case ADD: append_string("+"); break; + case SUB: append_string("-"); break; + case MUL: append_string("*"); break; + case DIV: append_string("/"); break; + default: break; + } + append_optional_space(); - void Inspect::operator()(SupportsDeclaration* sd) - { - append_string("("); - sd->feature()->perform(this); - append_string(": "); - sd->value()->perform(this); - append_string(")"); - } + bool pr = rcalc != nullptr && _parenthesizeCalculationRhs(op->op(), rcalc->op()); + pr |= op->op() == DIV && _parenthesizeCalculationRhsDiv(op->right()); - void Inspect::operator()(Supports_Interpolation* sd) - { - sd->value()->perform(this); - } + if (pr) append_string("("); + parentheses_opened = pl; + _writeCalculationValue(op->right()); + if (pr) append_string(")"); - void Inspect::operator()(Media_Query* mq) - { - size_t i = 0; - if (mq->media_type()) { - if (mq->is_negated()) append_string("not "); - else if (mq->is_restricted()) append_string("only "); - mq->media_type()->perform(this); } - else { - (*mq)[i++]->perform(this); - } - for (size_t L = mq->length(); i < L; ++i) { - append_string(" and "); - (*mq)[i]->perform(this); + else if (auto val = dynamic_cast(node)) { + val->accept(this); } - } - - void Inspect::operator()(Media_Query_Expression* mqe) - { - if (mqe->is_interpolated()) { - mqe->feature()->perform(this); + else if (auto val = dynamic_cast(node)) { + val->accept(this); } else { - append_string("("); - mqe->feature()->perform(this); - if (mqe->value()) { - append_string(": "); // verified - mqe->value()->perform(this); - } - append_string(")"); + append_string("[NOSUP]"); } - } - void Inspect::operator()(At_Root_Query* ae) - { - if (ae->feature()) { - append_string("("); - ae->feature()->perform(this); - if (ae->value()) { - append_colon_separator(); - ae->value()->perform(this); - } - append_string(")"); - } } - void Inspect::operator()(Function* f) + void Inspect::visitMixin(Mixin* mixin) { - append_token("get-function", f); - append_string("("); - append_string(quote(f->name())); - append_string(")"); + if (output_style() == SASS_STYLE_TO_CSS) { + throw Exception::InvalidCssValue({}, *mixin); + } + add_open_mapping(mixin, false); + append_string("get-mixin(\""); + if (mixin->callable() != nullptr) + append_string(mixin->callable()->name()); + append_string("\")"); + add_close_mapping(mixin, false); } - void Inspect::operator()(Null* n) + void Inspect::visitCalcOperation(CalcOperation* op) { - // output the final token - append_token("null", n); + //if (output_style() == SASS_STYLE_TO_CSS) return; + append_token(op->left()->toString(), op->left()); + if (op->op() == ADD) append_string(" + "); + else if (op->op() == SUB) append_string(" - "); + else if (op->op() == MUL) append_string(" * "); + else if (op->op() == DIV) append_string(" / "); + append_token(op->right()->toString(), op->right()); } - // parameters and arguments - void Inspect::operator()(Parameter* p) + void Inspect::visitNumber(Number* value) { - append_token(p->name(), p); - if (p->default_value()) { - append_colon_separator(); - p->default_value()->perform(this); - } - else if (p->is_rest_parameter()) { - append_string("..."); + if (value->lhsAsSlash() && value->rhsAsSlash()) { + visitNumber(value->lhsAsSlash()); + append_string("/"); + visitNumber(value->rhsAsSlash()); + return; } - } - void Inspect::operator()(Parameters* p) - { - append_string("("); - if (!p->empty()) { - (*p)[0]->perform(this); - for (size_t i = 1, L = p->length(); i < L; ++i) { - append_comma_separator(); - (*p)[i]->perform(this); + if (std::isnan(value->value())) { + append_string("calc("); + if (value->value() < 0) { + append_string("-NaN"); + } + else { + append_string("NaN"); + } + if (!value->isUnitless()) { + if (value->numerators.size() > 0) + append_string(" * 1"); + append_string(value->unit2()); } + append_string(")"); + return; } - append_string(")"); - } - void Inspect::operator()(Argument* a) - { - if (!a->name().empty()) { - append_token(a->name(), a); - append_colon_separator(); - } - if (!a->value()) return; - // Special case: argument nulls can be ignored - if (a->value()->concrete_type() == Expression::NULL_VAL) { + if (std::isinf(value->value())) { + append_string("calc("); + if (value->value() < 0) { + append_string("-infinity"); + } + else { + append_string("infinity"); + } + if (!value->isUnitless()) { + if (value->numerators.size() > 0) + append_string(" * 1"); + append_string(value->unit2()); + } + append_string(")"); return; } - if (a->value()->concrete_type() == Expression::STRING) { - String_Constant* s = Cast(a->value()); - if (s) s->perform(this); - } else { - a->value()->perform(this); + + // Avoid streams + char buf[255]; + snprintf(buf, 255, + outopt.nr_sprintf, + value->value()); + + // Operate from behind + char* end = buf; + + // Move to last position + while (*end != 0) ++end; + if (end != buf) end--; + // Delete trailing zeros + while (*end == '0') { + *end = 0; + end--; } - if (a->is_rest_argument()) { - append_string("..."); + // Delete trailing decimal separator + if (*end == '.') *end = 0; + + // Some final cosmetics + if (buf[0] == '-' && buf[1] == '0' && buf[2] == 0) { + buf[0] = '0'; buf[1] = 0; } - } - void Inspect::operator()(Arguments* a) - { - append_string("("); - if (!a->empty()) { - (*a)[0]->perform(this); - for (size_t i = 1, L = a->length(); i < L; ++i) { - append_string(", "); // verified - // Sass Bug? append_comma_separator(); - (*a)[i]->perform(this); + // add unit now + sass::string res(buf); + + if (true) + { + size_t iL = value->numerators.size(); + size_t nL = value->denominators.size(); + if (iL != 0) res += value->numerators[0]; + for (size_t i = 1; i < iL; i += 1) { + res += " * 1"; + res += value->numerators[i]; + } + for (size_t i = 0; i < nL; i += 1) { + res += " / 1"; + res += value->denominators[i]; } } - append_string(")"); - } + else { + res += value->unit(); + } - void Inspect::operator()(Selector_Schema* s) - { - s->contents()->perform(this); + if (value->isValidCssUnit()) { + // output the final token + append_token(res, value); + } + else { + append_string("calc("); + append_token(res, value); + append_string(")"); + } } - void Inspect::operator()(Parent_Reference* p) + void Inspect::visitString(String* value) { - append_string("&"); + add_open_mapping(value, true); + if (quotes && value->hasQuotes()) { + renderQuotedString(value->value()); + } + else { + renderUnquotedString(value->value()); + } + add_close_mapping(value, true); } - void Inspect::operator()(PlaceholderSelector* s) - { - append_token(s->name(), s); - - } - void Inspect::operator()(TypeSelector* s) - { - append_token(s->ns_name(), s); - } - void Inspect::operator()(ClassSelector* s) - { - append_token(s->ns_name(), s); - } - void Inspect::operator()(IDSelector* s) - { - append_token(s->ns_name(), s); - } - void Inspect::operator()(AttributeSelector* s) - { - append_string("["); - add_open_mapping(s); - append_token(s->ns_name(), s); - if (!s->matcher().empty()) { - append_string(s->matcher()); - if (s->value() && *s->value()) { - s->value()->perform(this); - } - } - add_close_mapping(s); - if (s->modifier() != 0) { - append_mandatory_space(); - append_char(s->modifier()); - } - append_string("]"); - } - void Inspect::operator()(PseudoSelector* s) - { - if (s->name() != "") { - append_string(":"); - if (s->isSyntacticElement()) { - append_string(":"); - } - append_token(s->ns_name(), s); - if (s->selector() || s->argument()) { - bool was = in_wrapped; - in_wrapped = true; - append_string("("); - if (s->argument()) { - s->argument()->perform(this); - } - if (s->selector() && s->argument()) { - append_mandatory_space(); - } - bool was_comma_array = in_comma_array; - in_comma_array = false; - if (s->selector()) { - s->selector()->perform(this); - } - in_comma_array = was_comma_array; - append_string(")"); - in_wrapped = was; - } - } - } - void Inspect::operator()(SelectorList* g) - { - if (g->empty()) { - if (output_style() == TO_SASS) { - append_token("()", g); - } - return; - } - bool was_comma_array = in_comma_array; - // probably ruby sass equivalent of element_needs_parens - if (output_style() == TO_SASS && g->length() == 1 && - (!Cast((*g)[0]) && - !Cast((*g)[0]))) { - append_string("("); - } - else if (!in_declaration && in_comma_array) { - append_string("("); - } - if (in_declaration) in_comma_array = true; - for (size_t i = 0, L = g->length(); i < L; ++i) { - if (!in_wrapped && i == 0) append_indentation(); - if ((*g)[i] == nullptr) continue; - if (g->at(i)->length() == 0) continue; - schedule_mapping(g->at(i)->last()); - // add_open_mapping((*g)[i]->last()); - (*g)[i]->perform(this); - // add_close_mapping((*g)[i]->last()); - if (i < L - 1) { - scheduled_space = 0; - append_comma_separator(); - } - } - in_comma_array = was_comma_array; - // probably ruby sass equivalent of element_needs_parens - if (output_style() == TO_SASS && g->length() == 1 && - (!Cast((*g)[0]) && - !Cast((*g)[0]))) { - append_string(",)"); - } - else if (!in_declaration && in_comma_array) { - append_string(")"); - } - } - void Inspect::operator()(ComplexSelector* sel) - { - if (sel->hasPreLineFeed()) { - append_optional_linefeed(); - if (!in_wrapped && output_style() == NESTED) { - append_indentation(); - } - } - const SelectorComponent* prev = nullptr; - for (auto& item : sel->elements()) { - if (prev != nullptr) { - if (item->getCombinator() || prev->getCombinator()) { - append_optional_space(); - } else { - append_mandatory_space(); - } - } - item->perform(this); - prev = item.ptr(); - } - } - void Inspect::operator()(SelectorComponent* sel) - { - // You should probably never call this method directly - // But in case anyone does, we will do the up-casting - if (auto comp = Cast(sel)) operator()(comp); - if (auto comb = Cast(sel)) operator()(comb); - } - void Inspect::operator()(CompoundSelector* sel) - { - if (sel->hasRealParent() /* || sel->has_real_parent_ref() */) { - append_string("&"); - } - for (auto& item : sel->elements()) { - item->perform(this); - } - // Add the post line break (from ruby sass) - // Dart sass uses another logic for newlines - if (sel->hasPostLineBreak()) { - if (output_style() != COMPACT) { - append_optional_linefeed(); - } - } - } - void Inspect::operator()(SelectorCombinator* sel) - { - append_optional_space(); - switch (sel->combinator()) { - case SelectorCombinator::Combinator::CHILD: append_string(">"); break; - case SelectorCombinator::Combinator::GENERAL: append_string("~"); break; - case SelectorCombinator::Combinator::ADJACENT: append_string("+"); break; - } - append_optional_space(); - // Add the post line break (from ruby sass) - // Dart sass uses another logic for newlines - if (sel->hasPostLineBreak()) { - if (output_style() != COMPACT) { - // append_optional_linefeed(); - } - } - } } diff --git a/src/inspect.hpp b/src/inspect.hpp index 2755b3b510..c24176fed7 100644 --- a/src/inspect.hpp +++ b/src/inspect.hpp @@ -1,99 +1,119 @@ -#ifndef SASS_INSPECT_H -#define SASS_INSPECT_H +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#ifndef SASS_INSPECT_HPP +#define SASS_INSPECT_HPP + +// sass.hpp must go before all system headers +// to get the __EXTENSIONS__ fix on Solaris. +#include "capi_sass.hpp" -#include "position.hpp" -#include "operation.hpp" #include "emitter.hpp" +#include "color_maps.hpp" +#include "visitor_css.hpp" +#include "visitor_value.hpp" +#include "visitor_selector.hpp" namespace Sass { - class Context; - class Inspect : public Operation_CRTP, public Emitter { - protected: - // import all the class-specific methods and override as desired - using Operation_CRTP::operator(); + // public SelectorVisitor + + // Inspect does roughly the same as serialize.dart + class Inspect : + public SelectorVisitor, + public ValueVisitor, + public CssVisitor, + public Emitter { public: - Inspect(const Emitter& emi); - virtual ~Inspect(); - - // statements - virtual void operator()(Block*); - virtual void operator()(StyleRule*); - virtual void operator()(Bubble*); - virtual void operator()(SupportsRule*); - virtual void operator()(AtRootRule*); - virtual void operator()(AtRule*); - virtual void operator()(Keyframe_Rule*); - virtual void operator()(Declaration*); - virtual void operator()(Assignment*); - virtual void operator()(Import*); - virtual void operator()(Import_Stub*); - virtual void operator()(WarningRule*); - virtual void operator()(ErrorRule*); - virtual void operator()(DebugRule*); - virtual void operator()(Comment*); - virtual void operator()(If*); - virtual void operator()(ForRule*); - virtual void operator()(EachRule*); - virtual void operator()(WhileRule*); - virtual void operator()(Return*); - virtual void operator()(ExtendRule*); - virtual void operator()(Definition*); - virtual void operator()(Mixin_Call*); - virtual void operator()(Content*); - // expressions - virtual void operator()(Map*); - virtual void operator()(Function*); - virtual void operator()(List*); - virtual void operator()(Binary_Expression*); - virtual void operator()(Unary_Expression*); - virtual void operator()(Function_Call*); - // virtual void operator()(Custom_Warning*); - // virtual void operator()(Custom_Error*); - virtual void operator()(Variable*); - virtual void operator()(Number*); - virtual void operator()(Color_RGBA*); - virtual void operator()(Color_HSLA*); - virtual void operator()(Boolean*); - virtual void operator()(String_Schema*); - virtual void operator()(String_Constant*); - virtual void operator()(String_Quoted*); - virtual void operator()(Custom_Error*); - virtual void operator()(Custom_Warning*); - virtual void operator()(SupportsOperation*); - virtual void operator()(SupportsNegation*); - virtual void operator()(SupportsDeclaration*); - virtual void operator()(Supports_Interpolation*); - virtual void operator()(MediaRule*); - virtual void operator()(CssMediaRule*); - virtual void operator()(CssMediaQuery*); - virtual void operator()(Media_Query*); - virtual void operator()(Media_Query_Expression*); - virtual void operator()(At_Root_Query*); - virtual void operator()(Null*); - virtual void operator()(Parent_Reference* p); - // parameters and arguments - virtual void operator()(Parameter*); - virtual void operator()(Parameters*); - virtual void operator()(Argument*); - virtual void operator()(Arguments*); - // selectors - virtual void operator()(Selector_Schema*); - virtual void operator()(PlaceholderSelector*); - virtual void operator()(TypeSelector*); - virtual void operator()(ClassSelector*); - virtual void operator()(IDSelector*); - virtual void operator()(AttributeSelector*); - virtual void operator()(PseudoSelector*); - virtual void operator()(SelectorComponent*); - virtual void operator()(SelectorCombinator*); - virtual void operator()(CompoundSelector*); - virtual void operator()(ComplexSelector*); - virtual void operator()(SelectorList*); - virtual sass::string lbracket(List*); - virtual sass::string rbracket(List*); + // Whether quoted strings should be emitted with quotes. + bool quotes; + + // So far this only influences how list are rendered. + // If the separator is known to be comma, we append + // a trailing comma for lists with a single item. + bool inspect; + + // We should probably pass an emitter, so we can switch implementation? + Inspect(const OutputOptions& opt); + Inspect(Logger& logger, const OutputOptions& opt); + + void visitBlockStatements(CssNodeVector children); + + void renderQuotedString(const sass::string& text, uint8_t quotes = 0); + void renderUnquotedString(const sass::string& text); + + bool _tryPrivateUseCharacter(uint8_t chr); + + ///////////////////////////////////////////////////////////////////////// + // Implement Selector Visitors + ///////////////////////////////////////////////////////////////////////// + + virtual void visitAttributeSelector(AttributeSelector* sel) override; + virtual void visitClassSelector(ClassSelector* sel) override; + virtual void visitComplexSelector(ComplexSelector* sel) override; + virtual void visitCompoundSelector(CompoundSelector* sel) override; + virtual void visitIDSelector(IDSelector* sel) override; + virtual void visitPlaceholderSelector(PlaceholderSelector* sel) override; + virtual void visitPseudoSelector(PseudoSelector* sel) override; + // virtual void visitSelectorCombinator(SelectorCombinator* sel) override; // LibSass only + virtual void visitSelectorList(SelectorList* sel) override; + virtual void visitTypeSelector(TypeSelector* sel) override; + + virtual void visitSelectorComponent(CplxSelComponent* sel); // LibSass only + virtual void visitSelectorCombinator(SelectorCombinator* sel); // LibSass only + + ///////////////////////////////////////////////////////////////////////// + // Implement Value Visitors + ///////////////////////////////////////////////////////////////////////// + + virtual void visitBoolean(Boolean* value) override; + virtual void visitColor(Color* value) override; + virtual void visitFunction(Function* value) override; + virtual void visitCalculation(Calculation* value) override; + virtual void visitCalcOperation(CalcOperation* value) override; + virtual void visitMixin(Mixin* value) override; + virtual void visitList(List* value) override; + virtual void visitMap(Map* value) override; + virtual void visitNull(Null* value) override; + virtual void visitNumber(Number* value) override; + virtual void visitString(String* value) override; + // Private helper for "messy" calc values + void _writeCalculationValue(AstNode* node, bool wrap = true); + void _writeCalculationUnits(Units* units); + + ///////////////////////////////////////////////////////////////////////// + // Implement CSS Visitors + ///////////////////////////////////////////////////////////////////////// + + virtual void visitCssAtRule(CssAtRule* css) override; + virtual void visitCssComment(CssComment* css) override; + virtual void visitCssDeclaration(CssDeclaration* css) override; + bool _IsInvisible(CssNode* node); + virtual void visitCssImport(CssImport* css) override; + virtual void visitCssKeyframeBlock(CssKeyframeBlock* css) override; + virtual void visitCssMediaRule(CssMediaRule* css) override; + virtual void visitCssRoot(CssRoot* css) override; // LibSass only + virtual void visitCssStyleRule(CssStyleRule* css) override; + virtual void visitCssSupportsRule(CssSupportsRule* css) override; + + ///////////////////////////////////////////////////////////////////////// + // Not part of visitors (used internally as entry points) + ///////////////////////////////////////////////////////////////////////// + + virtual void acceptCssString(const CssString*); + virtual void acceptCssMediaQuery(CssMediaQuery*); + virtual void acceptInterpolation(Interpolation*); + virtual void acceptNameSpaceSelector(SelectorNS*); + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + void _writeMapElement(Interpolant* value); + + template + bool _tryPrivateUseCharacter(const octet_iterator& it, const octet_iterator& end, size_t& offset); }; diff --git a/src/interpolation.cpp b/src/interpolation.cpp new file mode 100644 index 0000000000..993c1081d9 --- /dev/null +++ b/src/interpolation.cpp @@ -0,0 +1,96 @@ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#include "interpolation.hpp" + +namespace Sass { + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // Create new interpolation object from the interpolation buffer + Interpolation* InterpolationBuffer::getInterpolation(const SourceSpan& pstate, bool rtrim) + { + InterpolationObj itpl = SASS_MEMORY_NEW(Interpolation, pstate); + // Append all content Expressions + for (auto& item : contents) { + itpl->append(item); + } + if (!text.empty()) { + // Appends a ItplString from the remaining text in the string buffer + if (rtrim) StringUtils::makeRightTrimmed(text.buffer); + itpl->append(SASS_MEMORY_NEW(ItplString, pstate, text.buffer)); + } + // ToDo: get rid of detach? + return itpl.detach(); + } + + // Flushes [text] to [contents] if necessary. + void InterpolationBuffer::flushText() + { + // Do nothing if text is empty + if (text.empty()) return; + // Create and add string constant to container + contents.emplace_back(SASS_MEMORY_NEW( + ItplString, pstate, text.buffer)); + // Clear buffer now + text.clear(); + } + // EO flushText + + // Add an interpolation to the buffer. + void InterpolationBuffer::addInterpolation(const InterpolationObj schema) + { + if (schema->empty()) return; + + auto& elements = schema->elements(); + auto addStart = elements.begin(); + auto addEnd = elements.end(); + + // The schema to add start with a plain string + if (ItplString* str = elements[0]->isaItplString()) { + // Append the plain string + text.write(str); + // First item is consumed + addStart += 1; + } + + // Flush plain text to contents (as + // ItplString) if any text is defined. + flushText(); + + // Is there an item at the end? + if (addStart != addEnd) { + + auto& last = *(addEnd - 1); + if (auto str = last->isaItplString()) { + // Add the rest if some is left + contents.insert(contents.end(), + addStart, addEnd - 1); + text.write( + str->text(), + str->pstate()); + } + else { + // Add the rest if some is left + contents.insert(contents.end(), + addStart, addEnd); + } + } + + if (!contents.empty()) { + if (auto str = contents.back()->isaItplString()) { + text.write( + str->text(), + str->pstate()); + contents.pop_back(); + } + } + + } + // EO addInterpolation + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + +} diff --git a/src/interpolation.hpp b/src/interpolation.hpp new file mode 100644 index 0000000000..2ffdd3697a --- /dev/null +++ b/src/interpolation.hpp @@ -0,0 +1,200 @@ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#ifndef SASS_INTERPOLATION_H +#define SASS_INTERPOLATION_H + +// sass.hpp must go before all system headers +// to get the __EXTENSIONS__ fix on Solaris. +#include "capi_sass.hpp" + +#include "ast_fwd_decl.hpp" +#include "string_utils.hpp" +#include "scanner_string.hpp" +#include "utf8/checked.h" +#include "ast_values.hpp" + +namespace Sass { + + class StringBuffer { + + public: + + sass::string buffer; + + StringBuffer() : + buffer() + {} + + void writeCharCode(uint32_t character) + { + utf8::append(character, std::back_inserter(buffer)); + } + + void write(const ItplString* string) + { + buffer += string->text(); + } + + void write(unsigned char character) + { + buffer.push_back(character); + } + + void write(char character) + { + buffer.push_back(character); + } + + void write(const sass::string& text) + { + buffer.append(text); + } + + void write(const sass::string& text, const SourceSpan& pstate) + { + buffer.append(text); + } + + void write(sass::string&& text) + { + buffer.append(std::move(text)); + } + + void write(sass::string&& text, const SourceSpan& pstate) + { + buffer.append(std::move(text)); + } + + bool empty() const + { + return buffer.empty(); + } + + void clear() + { + buffer.clear(); + } + + }; + // EO StringBuffer + + class InterpolationBuffer { + + private: + + SourceSpan pstate; + + sass::vector contents; + + public: + + StringBuffer text; + + InterpolationBuffer(const SourceSpan& pstate) : + pstate(pstate), text() + {} + + InterpolationBuffer(const StringScanner& scanner) : + pstate(scanner.rawSpan()), text() + {} + + // Create new interpolation object from the interpolation buffer + Interpolation* getInterpolation(const SourceSpan& pstate, bool rtrim = false); + + bool empty() const + { + return contents.empty() && text.empty(); + } + + // Empties this buffer. + void clear() { + contents.clear(); + } + + private: + + // Flushes [_text] to [_contents] if necessary. + void flushText(); + + public: + + // Add an interpolation to the buffer. + void addInterpolation(const InterpolationObj schema); + + void writeCharCode(uint32_t character) + { + text.writeCharCode(character); + } + + void write(unsigned char character) + { + text.write(character); + } + + void write(char character) + { + text.write(character); + } + + void write(const char str[]) + { + text.write(sass::string(str)); + } + + void write(const sass::string& str) + { + text.write(str); + } + + void write(const sass::string& str, SourceSpan pstate) + { + text.write(str, pstate); + } + + void write(sass::string&& str) + { + text.write(std::move(str)); + } + + void write(sass::string&& str, SourceSpan pstate) + { + text.write(std::move(str), pstate); + } + + void write(const ItplString* str) + { + text.write(str->text(), str->pstate()); + } + + void write(const InterpolantObj& expression) + { + flushText(); + contents.emplace_back(expression); + } + + void add(const InterpolantObj& expression) + { + flushText(); + contents.emplace_back(expression); + } + + sass::string trailingString() + { + return text.buffer; + } + + bool trailingStringEndsWith(const sass::string& cmp) + { + sass::string tail(text.buffer); + // ToDo: trim shouldn't affect srcmap? + StringUtils::makeRightTrimmed(tail); + return StringUtils::endsWith(tail, cmp); + } + + }; + // EO InterpolationBuffer + +} + +#endif diff --git a/src/json.cpp b/src/json.cpp index f7d06e4c75..ff58c20373 100644 --- a/src/json.cpp +++ b/src/json.cpp @@ -1,25 +1,27 @@ -/* - Copyright (C) 2011 Joseph A. Adams (joeyadams3.14159@gmail.com) - All rights reserved. - - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in - all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - THE SOFTWARE. -*/ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +// Copyright (C) 2011 Joseph A. Adams (joeyadams3.14159@gmail.com) +// All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +/*****************************************************************************/ #ifdef _MSC_VER #define _CRT_SECURE_NO_WARNINGS @@ -30,13 +32,13 @@ // include utf8 library used by libsass // ToDo: replace internal json utf8 code -#include "utf8.h" +#include "unicode.hpp" -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include #if defined(_MSC_VER) && _MSC_VER < 1900 #include @@ -93,7 +95,9 @@ static void sb_grow(SB *sb, int need) do { alloc *= 2; } while (alloc < length + need); - + #ifdef _MSC_VER + #pragma warning(disable : 6308) + #endif sb->start = (char*) realloc(sb->start, alloc + 1); if (sb->start == NULL) out_of_memory(); diff --git a/src/json.hpp b/src/json.hpp index 05b35cd940..f890395751 100644 --- a/src/json.hpp +++ b/src/json.hpp @@ -1,3 +1,6 @@ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ /* Copyright (C) 2011 Joseph A. Adams (joeyadams3.14159@gmail.com) All rights reserved. @@ -21,11 +24,11 @@ THE SOFTWARE. */ -#ifndef CCAN_JSON_H -#define CCAN_JSON_H +#ifndef CCAN_JSON_HPP +#define CCAN_JSON_HPP -#include -#include +#include +#include typedef enum { JSON_NULL, diff --git a/src/kwd_arg_macros.hpp b/src/kwd_arg_macros.hpp deleted file mode 100644 index e135da7dea..0000000000 --- a/src/kwd_arg_macros.hpp +++ /dev/null @@ -1,28 +0,0 @@ -#ifndef SASS_KWD_ARG_MACROS_H -#define SASS_KWD_ARG_MACROS_H - -// Example usage: -// KWD_ARG_SET(Args) { -// KWD_ARG(Args, string, foo); -// KWD_ARG(Args, int, bar); -// ... -// }; -// -// ... and later ... -// -// something(Args().foo("hey").bar(3)); - -#define KWD_ARG_SET(set_name) class set_name - -#define KWD_ARG(set_name, type, name) \ -private: \ - type name##_; \ -public: \ - set_name& name(type name##__) { \ - name##_ = name##__; \ - return *this; \ - } \ - type name() { return name##_; } \ -private: - -#endif diff --git a/src/lexer.cpp b/src/lexer.cpp deleted file mode 100644 index b83c739460..0000000000 --- a/src/lexer.cpp +++ /dev/null @@ -1,122 +0,0 @@ -// sass.hpp must go before all system headers to get the -// __EXTENSIONS__ fix on Solaris. -#include "sass.hpp" - -#include -#include -#include "lexer.hpp" -#include "constants.hpp" -#include "util_string.hpp" - - -namespace Sass { - using namespace Constants; - - namespace Prelexer { - - //#################################### - // BASIC CHARACTER MATCHERS - //#################################### - - // Match standard control chars - const char* kwd_at(const char* src) { return exactly<'@'>(src); } - const char* kwd_dot(const char* src) { return exactly<'.'>(src); } - const char* kwd_comma(const char* src) { return exactly<','>(src); }; - const char* kwd_colon(const char* src) { return exactly<':'>(src); }; - const char* kwd_star(const char* src) { return exactly<'*'>(src); }; - const char* kwd_plus(const char* src) { return exactly<'+'>(src); }; - const char* kwd_minus(const char* src) { return exactly<'-'>(src); }; - const char* kwd_slash(const char* src) { return exactly<'/'>(src); }; - - bool is_number(char chr) { - return Util::ascii_isdigit(static_cast(chr)) || - chr == '-' || chr == '+'; - } - - // check if char is within a reduced ascii range - // valid in a uri (copied from Ruby Sass) - bool is_uri_character(char chr) - { - unsigned int cmp = unsigned(chr); - return (cmp > 41 && cmp < 127) || - cmp == ':' || cmp == '/' || cmp == '|'; - } - - // check if char is within a reduced ascii range - // valid for escaping (copied from Ruby Sass) - bool is_escapable_character(char chr) - { - unsigned int cmp = unsigned(chr); - return cmp > 31 && cmp < 127; - } - - // Match word character (look ahead) - bool is_character(char chr) - { - // valid alpha, numeric or unicode char (plus hyphen) - return Util::ascii_isalnum(static_cast(chr)) || - !Util::ascii_isascii(static_cast(chr)) || - chr == '-'; - } - - //#################################### - // BASIC CLASS MATCHERS - //#################################### - - // create matchers that advance the position - const char* space(const char* src) { return Util::ascii_isspace(static_cast(*src)) ? src + 1 : nullptr; } - const char* alpha(const char* src) { return Util::ascii_isalpha(static_cast(*src)) ? src + 1 : nullptr; } - const char* nonascii(const char* src) { return Util::ascii_isascii(static_cast(*src)) ? nullptr : src + 1; } - const char* digit(const char* src) { return Util::ascii_isdigit(static_cast(*src)) ? src + 1 : nullptr; } - const char* xdigit(const char* src) { return Util::ascii_isxdigit(static_cast(*src)) ? src + 1 : nullptr; } - const char* alnum(const char* src) { return Util::ascii_isalnum(static_cast(*src)) ? src + 1 : nullptr; } - const char* hyphen(const char* src) { return *src == '-' ? src + 1 : 0; } - const char* uri_character(const char* src) { return is_uri_character(*src) ? src + 1 : 0; } - const char* escapable_character(const char* src) { return is_escapable_character(*src) ? src + 1 : 0; } - - // Match multiple ctype characters. - const char* spaces(const char* src) { return one_plus(src); } - const char* digits(const char* src) { return one_plus(src); } - const char* hyphens(const char* src) { return one_plus(src); } - - // Whitespace handling. - const char* no_spaces(const char* src) { return negate< space >(src); } - const char* optional_spaces(const char* src) { return zero_plus< space >(src); } - - // Match any single character. - const char* any_char(const char* src) { return *src ? src + 1 : src; } - - // Match word boundary (zero-width lookahead). - const char* word_boundary(const char* src) { return is_character(*src) || *src == '#' ? 0 : src; } - - // Match linefeed /(?:\n|\r\n?|\f)/ - const char* re_linebreak(const char* src) - { - // end of file or unix linefeed return here - if (*src == 0) return src; - // end of file or unix linefeed return here - if (*src == '\n' || *src == '\f') return src + 1; - // a carriage return may optionally be followed by a linefeed - if (*src == '\r') return *(src + 1) == '\n' ? src + 2 : src + 1; - // no linefeed - return 0; - } - - // Assert string boundaries (/\Z|\z|\A/) - // This is a zero-width positive lookahead - const char* end_of_line(const char* src) - { - // end of file or unix linefeed return here - return *src == 0 || *src == '\n' || *src == '\r' || *src == '\f' ? src : 0; - } - - // Assert end_of_file boundary (/\z/) - // This is a zero-width positive lookahead - const char* end_of_file(const char* src) - { - // end of file or unix linefeed return here - return *src == 0 ? src : 0; - } - - } -} diff --git a/src/lexer.hpp b/src/lexer.hpp deleted file mode 100644 index 360ed22694..0000000000 --- a/src/lexer.hpp +++ /dev/null @@ -1,304 +0,0 @@ -#ifndef SASS_LEXER_H -#define SASS_LEXER_H - -#include - -namespace Sass { - namespace Prelexer { - - //#################################### - // BASIC CHARACTER MATCHERS - //#################################### - - // Match standard control chars - const char* kwd_at(const char* src); - const char* kwd_dot(const char* src); - const char* kwd_comma(const char* src); - const char* kwd_colon(const char* src); - const char* kwd_star(const char* src); - const char* kwd_plus(const char* src); - const char* kwd_minus(const char* src); - const char* kwd_slash(const char* src); - - //#################################### - // BASIC CLASS MATCHERS - //#################################### - - // Matches ASCII digits, +, and -. - bool is_number(char src); - - bool is_uri_character(char src); - bool escapable_character(char src); - - // Match a single ctype predicate. - const char* space(const char* src); - const char* alpha(const char* src); - const char* digit(const char* src); - const char* xdigit(const char* src); - const char* alnum(const char* src); - const char* hyphen(const char* src); - const char* nonascii(const char* src); - const char* uri_character(const char* src); - const char* escapable_character(const char* src); - - // Match multiple ctype characters. - const char* spaces(const char* src); - const char* digits(const char* src); - const char* hyphens(const char* src); - - // Whitespace handling. - const char* no_spaces(const char* src); - const char* optional_spaces(const char* src); - - // Match any single character (/./). - const char* any_char(const char* src); - - // Assert word boundary (/\b/) - // Is a zero-width positive lookaheads - const char* word_boundary(const char* src); - - // Match a single linebreak (/(?:\n|\r\n?)/). - const char* re_linebreak(const char* src); - - // Assert string boundaries (/\Z|\z|\A/) - // There are zero-width positive lookaheads - const char* end_of_line(const char* src); - - // Assert end_of_file boundary (/\z/) - const char* end_of_file(const char* src); - // const char* start_of_string(const char* src); - - // Type definition for prelexer functions - typedef const char* (*prelexer)(const char*); - - //#################################### - // BASIC "REGEX" CONSTRUCTORS - //#################################### - - // Match a single character literal. - // Regex equivalent: /(?:x)/ - template - const char* exactly(const char* src) { - return *src == chr ? src + 1 : 0; - } - - // Match the full string literal. - // Regex equivalent: /(?:literal)/ - template - const char* exactly(const char* src) { - if (str == NULL) return 0; - const char* pre = str; - if (src == NULL) return 0; - // there is a small chance that the search string - // is longer than the rest of the string to look at - while (*pre && *src == *pre) { - ++src, ++pre; - } - // did the matcher finish? - return *pre == 0 ? src : 0; - } - - - // Match a single character literal. - // Regex equivalent: /(?:x)/i - // only define lower case alpha chars - template - const char* insensitive(const char* src) { - return *src == chr || *src+32 == chr ? src + 1 : 0; - } - - // Match the full string literal. - // Regex equivalent: /(?:literal)/i - // only define lower case alpha chars - template - const char* insensitive(const char* src) { - if (str == NULL) return 0; - const char* pre = str; - if (src == NULL) return 0; - // there is a small chance that the search string - // is longer than the rest of the string to look at - while (*pre && (*src == *pre || *src+32 == *pre)) { - ++src, ++pre; - } - // did the matcher finish? - return *pre == 0 ? src : 0; - } - - // Match for members of char class. - // Regex equivalent: /[axy]/ - template - const char* class_char(const char* src) { - const char* cc = char_class; - while (*cc && *src != *cc) ++cc; - return *cc ? src + 1 : 0; - } - - // Match for members of char class. - // Regex equivalent: /[axy]+/ - template - const char* class_chars(const char* src) { - const char* p = src; - while (class_char(p)) ++p; - return p == src ? 0 : p; - } - - // Match for members of char class. - // Regex equivalent: /[^axy]/ - template - const char* neg_class_char(const char* src) { - if (*src == 0) return 0; - const char* cc = neg_char_class; - while (*cc && *src != *cc) ++cc; - return *cc ? 0 : src + 1; - } - - // Match for members of char class. - // Regex equivalent: /[^axy]+/ - template - const char* neg_class_chars(const char* src) { - const char* p = src; - while (neg_class_char(p)) ++p; - return p == src ? 0 : p; - } - - // Match all except the supplied one. - // Regex equivalent: /[^x]/ - template - const char* any_char_but(const char* src) { - return (*src && *src != chr) ? src + 1 : 0; - } - - // Succeeds if the matcher fails. - // Aka. zero-width negative lookahead. - // Regex equivalent: /(?!literal)/ - template - const char* negate(const char* src) { - return mx(src) ? 0 : src; - } - - // Succeeds if the matcher succeeds. - // Aka. zero-width positive lookahead. - // Regex equivalent: /(?=literal)/ - // just hangs around until we need it - template - const char* lookahead(const char* src) { - return mx(src) ? src : 0; - } - - // Tries supplied matchers in order. - // Succeeds if one of them succeeds. - // Regex equivalent: /(?:FOO|BAR)/ - template - const char* alternatives(const char* src) { - const char* rslt; - if ((rslt = mx(src))) return rslt; - return 0; - } - template - const char* alternatives(const char* src) { - const char* rslt; - if ((rslt = mx1(src))) return rslt; - return alternatives(src); - } - - // Tries supplied matchers in order. - // Succeeds if all of them succeeds. - // Regex equivalent: /(?:FOO)(?:BAR)/ - template - const char* sequence(const char* src) { - const char* rslt = src; - if (!(rslt = mx1(rslt))) return 0; - return rslt; - } - template - const char* sequence(const char* src) { - const char* rslt = src; - if (!(rslt = mx1(rslt))) return 0; - return sequence(rslt); - } - - - // Match a pattern or not. Always succeeds. - // Regex equivalent: /(?:literal)?/ - template - const char* optional(const char* src) { - const char* p = mx(src); - return p ? p : src; - } - - // Match zero or more of the patterns. - // Regex equivalent: /(?:literal)*/ - template - const char* zero_plus(const char* src) { - const char* p = mx(src); - while (p) src = p, p = mx(src); - return src; - } - - // Match one or more of the patterns. - // Regex equivalent: /(?:literal)+/ - template - const char* one_plus(const char* src) { - const char* p = mx(src); - if (!p) return 0; - while (p) src = p, p = mx(src); - return src; - } - - // Match mx non-greedy until delimiter. - // Other prelexers are greedy by default. - // Regex equivalent: /(?:$mx)*?(?=$delim)\b/ - template - const char* non_greedy(const char* src) { - while (!delim(src)) { - const char* p = mx(src); - if (p == src) return 0; - if (p == 0) return 0; - src = p; - } - return src; - } - - //#################################### - // ADVANCED "REGEX" CONSTRUCTORS - //#################################### - - // Match with word boundary rule. - // Regex equivalent: /(?:$mx)\b/i - template - const char* keyword(const char* src) { - return sequence < - insensitive < str >, - word_boundary - >(src); - } - - // Match with word boundary rule. - // Regex equivalent: /(?:$mx)\b/ - template - const char* word(const char* src) { - return sequence < - exactly < str >, - word_boundary - >(src); - } - - template - const char* loosely(const char* src) { - return sequence < - optional_spaces, - exactly < chr > - >(src); - } - template - const char* loosely(const char* src) { - return sequence < - optional_spaces, - exactly < str > - >(src); - } - - } -} - -#endif diff --git a/src/listize.cpp b/src/listize.cpp deleted file mode 100644 index d12eb0d2e9..0000000000 --- a/src/listize.cpp +++ /dev/null @@ -1,70 +0,0 @@ -// sass.hpp must go before all system headers to get the -// __EXTENSIONS__ fix on Solaris. -#include "sass.hpp" - -#include -#include -#include - -#include "listize.hpp" -#include "context.hpp" -#include "backtrace.hpp" -#include "error_handling.hpp" - -namespace Sass { - - Listize::Listize() - { } - - Expression* Listize::perform(AST_Node* node) - { - Listize listize; - return node->perform(&listize); - } - - Expression* Listize::operator()(SelectorList* sel) - { - List_Obj l = SASS_MEMORY_NEW(List, sel->pstate(), sel->length(), SASS_COMMA); - l->from_selector(true); - for (size_t i = 0, L = sel->length(); i < L; ++i) { - if (!sel->at(i)) continue; - l->append(sel->at(i)->perform(this)); - } - if (l->length()) return l.detach(); - return SASS_MEMORY_NEW(Null, l->pstate()); - } - - Expression* Listize::operator()(CompoundSelector* sel) - { - sass::string str; - for (size_t i = 0, L = sel->length(); i < L; ++i) { - Expression* e = (*sel)[i]->perform(this); - if (e) str += e->to_string(); - } - return SASS_MEMORY_NEW(String_Quoted, sel->pstate(), str); - } - - Expression* Listize::operator()(ComplexSelector* sel) - { - List_Obj l = SASS_MEMORY_NEW(List, sel->pstate()); - // ToDo: investigate what this does - // Note: seems reated to parent ref - l->from_selector(true); - - for (auto component : sel->elements()) { - if (CompoundSelectorObj compound = Cast(component)) { - if (!compound->empty()) { - ExpressionObj hh = compound->perform(this); - if (hh) l->append(hh); - } - } - else if (component) { - l->append(SASS_MEMORY_NEW(String_Quoted, component->pstate(), component->to_string())); - } - } - - if (l->length() == 0) return 0; - return l.detach(); - } - -} diff --git a/src/listize.hpp b/src/listize.hpp deleted file mode 100644 index 5da094df97..0000000000 --- a/src/listize.hpp +++ /dev/null @@ -1,37 +0,0 @@ -#ifndef SASS_LISTIZE_H -#define SASS_LISTIZE_H - -// sass.hpp must go before all system headers to get the -// __EXTENSIONS__ fix on Solaris. -#include "sass.hpp" - -#include "ast_fwd_decl.hpp" -#include "operation.hpp" - -namespace Sass { - - struct Backtrace; - - class Listize : public Operation_CRTP { - - public: - - static Expression* perform(AST_Node* node); - - public: - Listize(); - ~Listize() { } - - Expression* operator()(SelectorList*); - Expression* operator()(ComplexSelector*); - Expression* operator()(CompoundSelector*); - - // generic fallback - template - Expression* fallback(U x) - { return Cast(x); } - }; - -} - -#endif diff --git a/src/logger.cpp b/src/logger.cpp new file mode 100644 index 0000000000..c855ca79c7 --- /dev/null +++ b/src/logger.cpp @@ -0,0 +1,644 @@ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#include "logger.hpp" + +#include +#include "file.hpp" +#include "source.hpp" +#include "utf8/checked.h" +#include "string_utils.hpp" + +namespace Sass { + + // Default constructor + Logger::Logger(bool colors, bool unicode, int precision, size_t columns) : + epsilon(std::pow(0.1, precision + 1)), + columns(columns), + support_colors(colors), + support_unicode(unicode) + {} + + // Auto-detect if colors and unicode is supported + // Mostly depending if a terminal is connected + // Additionally fetches available terminal columns + void Logger::autodetectCapabalities() + { + setLogColors(Terminal::hasColorSupport(true)); + setLogUnicode(Terminal::hasUnicodeSupport(true)); + setLogColumns(Terminal::getColumns(true)); + } + // EO autodetectCapabalities + + // Enable terminal ANSI color support + void Logger::setLogColors(bool enable) + { + support_colors = enable; + } + // EO setLogColors + + // Enable terminal unicode support + void Logger::setLogUnicode(bool enable) + { + support_unicode = enable; + } + // EO setLogUnicode + + // Set available columns to break debug text + void Logger::setLogColumns(size_t columns) + { + // Clamp into a sensible range + if (columns > 800) { columns = 800; } + else if (columns < 40) { columns = 40; } + this->columns = columns; + } + // EO setLogColumns + + // Precision for numbers to be printed + void Logger::setPrecision(int precision) + { + epsilon = std::pow(0.1, precision + 1); + } + // EO setPrecision + + // Write warning header to error stream + void Logger::writeWarnHead(bool deprecation) + { + if (support_colors) { + logstrm << getTerm(Terminal::yellow); + if (!deprecation) logstrm << "Warning"; + else logstrm << "Deprecation Warning"; + logstrm << getTerm(Terminal::reset); + } + else { + if (!deprecation) logstrm << "WARNING"; + else logstrm << "DEPRECATION WARNING"; + } + } + // EO writeWarnHead + + // Print the `input` string onto the output stream `os` and + // wrap words around to fit into the given column `width`. + void print_wrapped(sass::string const& input, size_t width, sass::ostream& os) + { + sass::istream in(input); + + size_t current = 0; + sass::string word; + + while (in >> word) { + if (current + word.size() > width) { + os << STRMLF; + current = 0; + } + os << word << ' '; + current += word.size() + 1; + while (Character::isNewline(in.peek())) { + if (in.peek() == '\n') { + os << STRMLF; + current = 0; + } + in.ignore(1); + } + // Check if new line starts with white-space + while (Character::isSpaceOrTab(in.peek())) { + in.ignore(1); + // Preserve if we have multiple white-space + if (Character::isSpaceOrTab(in.peek())) os << ' '; + while (Character::isSpaceOrTab(in.peek())) { + in.ignore(1); + os << ' '; + } + } + } + if (current != 0) { + os << STRMLF; + } + } + // EO wrap + + // Print a warning without any SourceSpan (used by @warn) + void Logger::addWarning(const sass::string& message, enum WarningType type) + { + writeWarnHead(false); + logstrm << ": "; + + print_wrapped(message, 80, logstrm); + StackTraces stack(callStack.begin(), callStack.end()); + writeStackTraces(logstrm, stack, " ", true, 0); + } + // EO addWarning + + // Print a debug message without any SourceSpan (used by @debug) + void Logger::addDebug(const sass::string& message, const SourceSpan& pstate) + { + logstrm << pstate.getDebugPath() << ":" << + pstate.getLine() << " DEBUG: " << message; + logstrm << STRMLF; + } + // EO addDebug + + // Print a regular warning or deprecation + void Logger::printWarning(const sass::string& message, const SourceSpan& pstate, enum WarningType type, bool deprecation) + { + + callStackFrame frame(*this, pstate); + + if (reported[type]) { + if (type != WARN_RULE) { + suppressed += 1; + return; + } + } + + writeWarnHead(deprecation); + logstrm << " on line " << pstate.getLine(); + logstrm << ", column " << pstate.getColumn(); + logstrm << " of " << pstate.getDebugPath() << ':' << STRMLF; + + // Capped at 80 to keep specs backward compatible + print_wrapped(message, 80, logstrm); + + logstrm << STRMLF; + + StackTraces stack(callStack.begin(), callStack.end()); + writeStackTraces(logstrm, stack, " ", true); + + reported[type] = true; + } + // EO printWarning + + // Format and print source-span + void Logger::printSourceSpan( + SourceSpan pstate, + sass::ostream& stream, + bool unicode) + { + + // ASCII reporting + sass::string top(","); + sass::string upper("/"); + sass::string middle("|"); + sass::string lower("\\"); + sass::string runin("-"); + sass::string bottom("'"); + + // Or use Unicode versions + if (unicode) { + top = "\xE2\x95\xB7"; + upper = "\xE2\x94\x8C"; + middle = "\xE2\x94\x82"; + lower = "\xE2\x94\x94"; + runin = "\xE2\x94\x80"; + bottom = "\xE2\x95\xB5"; + } + + // now create the code trace (ToDo: maybe have utility functions?) + if (pstate.getContent() == nullptr) return; + + // Calculate offset positions + Offset beg = pstate.position; + Offset end = beg + pstate.span; + + // Dart-sass claims this might fail due to float errors + size_t padding = (size_t)floor(log10(end.line + 1)) + 1; + + // Do multi-line reporting + SourceData* source = pstate.getSource(); + if (pstate.span.line > 0 && source != nullptr) { + + sass::vector lines; + + // Fetch all lines we need to print the state //XOXOXO + for (size_t i = 0; i <= pstate.span.line; i++) { + sass::string line(source->getLine(pstate.position.line + i)); + lines.emplace_back(line); + } + + // Write intro line + stream + << getTerm(Terminal::blue) + << std::right << std::setfill(' ') + << std::setw(padding) << ' ' + << ' ' << top + << getTerm(Terminal::reset) + << STRMLF; + + for (size_t i = 0; i < lines.size(); i++) { + + // Right trim the line to report + StringUtils::makeRightTrimmed(lines[i]); + + // Write the line number and the code + stream + << getTerm(Terminal::blue) + << std::right << std::setfill(' ') + << std::setw(padding) << (beg.line + i + 1) + << ' ' << middle + << getTerm(Terminal::reset) + << ' '; + + // Report the first line + // Gets a line underneath + if (i == 0) { + + // Rewind position to last relevant to report cleaner if possible + while (beg.column > 0 && Character::isWhitespace(lines[i][beg.column - 1])) beg.column--; + + // Only need a line if not at start + if (beg.column > 0) { + + // Print the initial code line + auto line_beg = lines[i].begin(); + utf8::advance(line_beg, beg.column, lines[i].end()); + lines[i].insert(line_beg - lines[i].begin(), getTerm(Terminal::red)); + stream << " " << lines[i] << getTerm(Terminal::reset) << STRMLF; + + // Print the line beneath + stream + << getTerm(Terminal::blue) + << std::right << std::setfill(' ') + << std::setw(padding) << ' ' + << ' ' << middle << ' ' + << getTerm(Terminal::reset) + << getTerm(Terminal::red) + << upper; + // This needs a loop unfortunately + size_t cols = beg.column; + for (size_t n = 0; n < cols + 2; n++) { + stream << runin; + } + // Final indicator + stream + << '^' + << getTerm(Terminal::reset) + << STRMLF; + } + // Just print the code line + else { + stream + << getTerm(Terminal::red) + << upper << ' ' << lines[i] + << getTerm(Terminal::reset) + << STRMLF; + } + } + // Last line might get another indicator line + else if (i == lines.size() - 1) { + + if (end.column < lines[i].size()) { + + // Print the final code line + auto line_beg = lines[i].begin(); + utf8::advance(line_beg, end.column, lines[i].end()); + lines[i].insert(line_beg - lines[i].begin(), getTerm(Terminal::reset)); + stream << getTerm(Terminal::red) << middle << ' ' << lines[i] << STRMLF; + + // Print the line beneath + stream + << getTerm(Terminal::blue) + << std::right << std::setfill(' ') + << std::setw(padding) << ' ' + << ' ' << middle << ' ' + << getTerm(Terminal::reset) + << getTerm(Terminal::red) + << lower; + // This needs a loop unfortunately + for (size_t n = 0; n < end.column; n++) { + stream << runin; + } + // Final indicator + stream + << '^' + << getTerm(Terminal::reset) + << STRMLF; + } + else { + // Just print the code line + stream + << getTerm(Terminal::red) + << lower << ' ' << lines[i] + << getTerm(Terminal::reset) + << STRMLF; + } + } + else { + // Just print the code line + stream + << getTerm(Terminal::red) + << middle << ' ' << lines[i] + << getTerm(Terminal::reset) + << STRMLF; + } + } + + // Write outro line + stream + << getTerm(Terminal::blue) + << std::right << std::setfill(' ') + << std::setw(padding) << ' ' + << ' ' << bottom + << getTerm(Terminal::reset) + << STRMLF; + + } + // Single line reporting + else { + + sass::string raw = pstate.getSource()->getLine(pstate.position.line); + sass::string line; utf8::replace_invalid(raw.begin(), raw.end(), + std::back_inserter(line), unicode ? 0xfffd : '?'); + + // Convert to ASCII only string + if (!unicode) { + std::replace_if(line.begin(), line.end(), + Character::isUtf8StartByte, '?'); + line.erase(std::remove_if(line.begin(), line.end(), + Character::isUtf8Continuation), line.end()); + } + + size_t lhs_len, mid_len; //, rhs_len; + // Get the sizes (characters) for each part + mid_len = pstate.span.column; + lhs_len = pstate.position.column; + + // Normalize tab characters to spaces for better counting + for (size_t i = line.length(); i != std::string::npos; i -= 1) { + if (line[i] == '\t') { + // Adjust highlight positions + if (i < lhs_len) lhs_len += 3; + else if (i < lhs_len + mid_len) mid_len += 3; + // Replace tab with spaces + line.replace(i, 1, " "); + } + } + + // Split line into parts to report + // They will be shortened if needed + sass::string lhs, mid, rhs; + splitLine(line, lhs_len, mid_len, + columns - 4 - padding, lhs, mid, rhs); + + // Get character length of each part + lhs_len = utf8::distance(lhs.begin(), lhs.end()); + mid_len = utf8::distance(mid.begin(), mid.end()); + // rhs_len = utf8::distance(rhs.begin(), rhs.end()); + + // Report the trace + stream + + // Write the leading line + << getTerm(Terminal::blue) + << std::right << std::setfill(' ') + << std::setw(padding) << ' ' + << ' ' << top + << getTerm(Terminal::reset) + + << STRMLF + + // Write the line number + << getTerm(Terminal::blue) + << std::right << std::setfill(' ') + << std::setw(padding) << (beg.line + 1) + << ' ' << middle + << getTerm(Terminal::reset) + + << ' ' + + // Write the parts + << lhs + << getTerm(Terminal::red) + << mid + << getTerm(Terminal::reset) + << rhs + + << STRMLF + + // Write left part of marker line + << getTerm(Terminal::blue) + << std::right << std::setfill(' ') + << std::setw(padding) << ' ' + << ' ' << middle + << getTerm(Terminal::reset) + + << ' ' + + // Write the actual marker + << sass::string(lhs_len, ' ') + << getTerm(Terminal::red) + << sass::string(mid_len ? mid_len : 1, '^') + << getTerm(Terminal::reset) + + << STRMLF + + // Write the trailing line + << getTerm(Terminal::blue) + << std::right << std::setfill(' ') + << std::setw(padding) << ' ' + << ' ' << bottom + << getTerm(Terminal::reset) + + << STRMLF; + + } + + } + // EO printSourceSpan + + // Helper function for `printSourceSpan` to split lines to be printed + void Logger::splitLine(sass::string line, size_t lhs_len, size_t mid_len, + size_t columns, sass::string& lhs, sass::string& mid, sass::string& rhs) + { + + // Get the ellipsis character(s) either in unicode or ASCII + size_t ellipsis_len = support_unicode ? 1 : 3; + sass::string ellipsis(support_unicode ? "\xE2\x80\xA6" : "..."); + + // Normalize tab characters to spaces for better counting + for (size_t i = line.length(); i != std::string::npos; i -= 1) { + if (line[i] == '\t') line.replace(i, 1, " "); + } + + // Get line iterators + auto line_beg = line.begin(); + auto line_end = line.end(); + + // Get the sizes (characters) for each part + size_t line_len = utf8::distance(line_beg, line_end); + size_t rhs_len = line_len - lhs_len - mid_len; + + // Prepare iterators for left side of highlighted string + auto lhs_beg = line.begin(), lhs_end = lhs_beg; + utf8::advance(lhs_end, lhs_len, line_end); + // Prepare iterators for right side of highlighted string + auto rhs_beg = lhs_end, rhs_end = line.end(); + utf8::advance(rhs_beg, mid_len, line_end); + + // Create substring of each part + lhs.append(lhs_beg, lhs_end); + rhs.append(rhs_beg, rhs_end); + mid.append(lhs_end, rhs_beg); + + // Trim trailing spaces + StringUtils::makeRightTrimmed(rhs); + + // Re-count characters after trimming is done + lhs_len = utf8::distance(lhs.begin(), lhs.end()); + rhs_len = utf8::distance(rhs.begin(), rhs.end()); + + // Get how much we want to show at least + size_t min_left = 12, min_right = 12; + // But we can't show more than we have + min_left = std::min(min_left, lhs_len); + min_right = std::min(min_right, rhs_len); + + // Calculate available size for highlighted part + size_t mid_max = columns - min_left - min_right; + + // Check if middle parts needs shortening + if (mid_len > mid_max) { + // Calculate how much we must shorten + size_t shorten = mid_len - mid_max + ellipsis_len; + // Get the sizes for left and right parts and adjust for epsilon + size_t lhs_size = size_t(std::ceil(0.5 * (mid_len - shorten))); + size_t rhs_size = size_t(std::floor(0.5 * (mid_len - shorten))); + // Prepare iterators for later substring operation + auto lhs_start = mid.begin(), rhs_stop = mid.end(); + auto lhs_stop = lhs_start; utf8::advance(lhs_stop, lhs_size, rhs_stop); + auto rhs_start = lhs_stop; utf8::advance(rhs_start, shorten, rhs_stop); + // Recreate shortened middle (highlight) part + mid = sass::string(lhs_start, lhs_stop) + + ellipsis + sass::string(rhs_start, rhs_stop); + // Update middle size (should be mid_max) + mid_len = lhs_size + rhs_size + ellipsis_len; + } + else { + // We can give some space back + size_t leftover = mid_max - mid_len; + // Distribute the leftovers + // ToDo: do it with math only + while (leftover) { + if (min_left < lhs_len) { + min_left += 1; + leftover -= 1; + if (min_right < rhs_len) { + if (leftover > 0) { + min_right += 1; + leftover -= 1; + } + } + } + else if (min_right < rhs_len) { + min_right += 1; + leftover -= 1; + } + else { + break; + } + } + } + + // Shorten left side + if (min_left < lhs_len) { + min_left = min_left - ellipsis_len; + auto beg = lhs.begin(), end = lhs.end(); + utf8::advance(beg, lhs_len - min_left, end); + lhs = sass::string(beg, end); + StringUtils::makeLeftTrimmed(lhs); + lhs = ellipsis + lhs; + } + + // Shorten right side + if (min_right < rhs_len) { + min_right = min_right - ellipsis_len; + auto beg = rhs.begin(), end = rhs.begin(); + utf8::advance(end, min_right, rhs.end()); + rhs = sass::string(beg, end); + StringUtils::makeRightTrimmed(rhs); + rhs = rhs + ellipsis; + } + + } + // EO splitLine + + // Print `amount` of `traces` to output stream `os`. + void Logger::writeStackTraces(sass::ostream& os, StackTraces traces, + sass::string indent, bool showPos, size_t amount) + { + sass::sstream strm; + + // bool first = true; + size_t max = 0; + size_t i_beg = traces.size() - 1; + size_t i_end = sass::string::npos; + + + sass::string last("root stylesheet"); + sass::vector> traced; + for (size_t i = 0; i < traces.size(); i++) { + + const StackTrace& trace = traces[i]; + + // make path relative to the current directory + sass::string rel_path(File::abs2rel(trace.pstate.getAbsPath(), CWD(), CWD())); + + strm.str(sass::string()); + strm << rel_path << ' '; + strm << trace.pstate.getLine(); + strm << ":" << trace.pstate.getColumn(); + + sass::string str(strm.str()); + max = std::max(max, str.length()); + + if (i == 0) { + traced.emplace_back(std::make_pair( + str, last)); + } + else if (!traces[i - 1].name.empty()) { + last = traces[i - 1].name; + if (traces[i - 1].fn) last += "()"; + traced.emplace_back(std::make_pair(str, last)); + } + else { + traced.emplace_back(std::make_pair( + str, last)); + } + + } + + i_beg = traces.size() - 1; + i_end = sass::string::npos; + const StackTrace* prev = nullptr; + for (size_t i = i_beg; i != i_end; i--) { + + const StackTrace& trace = traces[i]; + + // make path relative to the current directory + sass::string rel_path(File::abs2rel(trace.pstate.getAbsPath(), CWD(), CWD())); + + // skip functions on error cases (unsure why ruby sass does this) + // if (trace.caller.substr(0, 6) == ", in f") continue; + + if (amount == sass::string::npos || amount > 0) { + if (prev && *prev == trace) continue; + printSourceSpan(trace.pstate, os, support_unicode); + if (amount > 0) --amount; + prev = &trace; + } + + if (showPos) { + os << indent; + os << std::left << std::setfill(' ') + << std::setw(max + 2) << traced[i].first; + os << traced[i].second; + os << STRMLF; + } + + } + + os << STRMLF; + + } + // EO writeStackTraces + +} diff --git a/src/logger.hpp b/src/logger.hpp new file mode 100644 index 0000000000..5f176c661b --- /dev/null +++ b/src/logger.hpp @@ -0,0 +1,175 @@ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#ifndef SASS_LOGGER_HPP +#define SASS_LOGGER_HPP + +// sass.hpp must go before all system headers +// to get the __EXTENSIONS__ fix on Solaris. +#include "capi_sass.hpp" + +#include +#include +#include "strings.hpp" +#include "constants.hpp" +#include "backtrace.hpp" +#include "callstack.hpp" +#include "terminal.hpp" + +namespace Sass { + + void print_wrapped(sass::string const& input, size_t width, sass::ostream& os); + + // The logger belongs to context + class Logger { + + public: + + enum WarningType { + WARN_MATH_DIV, + WARN_CAPI_FN, + WARN_ANGLE_CONVERT, + WARN_NUMBER_ARG, + WARN_NUMBER_PERCENT, + WARN_GLOBAL_ASSIGN, + WARN_COLOR_ITPL, + WARN_EMPTY_SELECTOR, + WARN_DOUBLE_PARENT, + WARN_STRING_CALL, + WARN_MOZ_DOC, + WARN_RULE + }; + + // Epsilon for precision + double epsilon; + + // warning buffers + sass::ostream logstrm; + + // The current callstack + BackTraces callStack; + + // Available columns on tty + size_t columns; + + // Flags for unicode and color + bool support_colors = false; + bool support_unicode = false; + + // Helper function to ease color output. Returns the + // passed color if color output is enable, otherwise + // it will simply return an empty string. + inline const char* getTerm(const char* color) { + if (support_colors) { + return color; + } + return Constants::String::empty; + } + + // Remember which warnings have already reported. + // Only report them once and just write an additional + // line reporting how many additional warnings are waiting + std::bitset<32> reported; // sizeof(Logger::WarningType) + + // How many warnings have been suppressed + int suppressed = 0; + + private: + + // Split the line to three parts for error reporting. + // The `lhs_len` is the position where the error reporting + // should start and `mid_len` how many characters should be + // highlighted. It will fill the strings `lhs`, `mid` and `rhs` + // accordingly so the total length is not bigger than `columns`. + // Strings might be shortened and ellipsis are added as needed. + void splitLine( + sass::string line, + size_t lhs_len, + size_t mid_len, + size_t columns, + sass::string& lhs, + sass::string& mid, + sass::string& rhs); + + private: + + // Write warning header to error stream + void writeWarnHead( + bool deprecation = false); + + public: + + // Print to stderr stream + void printWarning( + const sass::string& message, + const SourceSpan& pstate, + enum WarningType type, + bool deprecation = false); + + private: + + // Format and print source-span + void printSourceSpan( + SourceSpan pstate, + sass::ostream& stream, + bool unicode = false); + + public: + + // Print `amount` of `traces` to output stream `os`. + void writeStackTraces(sass::ostream& os, + StackTraces traces, + sass::string indent = " ", + bool showPos = true, + size_t showTraces = sass::string::npos); + + public: + + // Default constructor + Logger(bool colors = false, bool unicode = false, + int precision = SassDefaultPrecision, size_t columns = NPOS); + + // Auto-detect if colors and unicode is supported + // Mostly depending if a terminal is connected + // Additionally fetches available terminal columns + void autodetectCapabalities(); + + // Enable terminal ANSI color support + void setLogColors(bool enable); + // Enable terminal unicode support + void setLogUnicode(bool enable); + // Set available columns to break debug text + void setLogColumns(size_t columns = NPOS); + + // Precision for numbers to be printed + void setPrecision(int precision); + + // Print a warning without any SourceSpan (used by @warn) + void addWarning(const sass::string& message, enum WarningType); + + // Print a debug message without any SourceSpan (used by @debug) + void addDebug(const sass::string& message, const SourceSpan& pstate); + + // Print a warning with SourceSpan attached (used internally) + void addWarning(const sass::string& message, const SourceSpan& pstate, enum WarningType type) + { + printWarning(message, pstate, type, false); + } + + // Print a deprecation warning with SourceSpan attached (used internally) + void addDeprecation(const sass::string& message, const SourceSpan& pstate, enum WarningType type) + { + printWarning(message, pstate, type, true); + } + + public: + + // Implicitly allow conversion + operator BackTraces&() { + return callStack; } + + }; + +} + +#endif diff --git a/src/mapping.hpp b/src/mapping.hpp index d6a3336aa7..80f6f2a828 100644 --- a/src/mapping.hpp +++ b/src/mapping.hpp @@ -1,19 +1,33 @@ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ #ifndef SASS_MAPPING_H #define SASS_MAPPING_H -#include "position.hpp" -#include "backtrace.hpp" +#include +#include "memory.hpp" +#include "offset.hpp" namespace Sass { - struct Mapping { - Position original_position; - Position generated_position; + class Mapping { + + public: + // Source Index for the origin + size_t srcidx; // uint32_t + // Position of original occurrence + Offset origin; + // Position where it was rendered. + Offset target; + + // Base copy constructor + Mapping(size_t srcidx, const Offset& origin, const Offset& target) + : srcidx(srcidx), origin(origin), target(target) { } - Mapping(const Position& original_position, const Position& generated_position) - : original_position(original_position), generated_position(generated_position) { } }; + typedef sass::vector Mappings; + } #endif diff --git a/src/memory.hpp b/src/memory.hpp index 14a5541b6d..af820d79e0 100644 --- a/src/memory.hpp +++ b/src/memory.hpp @@ -1,12 +1,15 @@ -#ifndef SASS_MEMORY_H -#define SASS_MEMORY_H +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#ifndef SASS_MEMORY_HPP +#define SASS_MEMORY_HPP #include "settings.hpp" // Include memory headers -#include "memory/config.hpp" -#include "memory/allocator.hpp" -#include "memory/shared_ptr.hpp" -#include "memory/memory_pool.hpp" +#include "memory_config.hpp" +#include "memory_allocator.hpp" +#include "memory_pool.hpp" +#include "shared_ptr.hpp" #endif diff --git a/src/memory/allocator.cpp b/src/memory/allocator.cpp deleted file mode 100644 index 7ad4a542ab..0000000000 --- a/src/memory/allocator.cpp +++ /dev/null @@ -1,48 +0,0 @@ -#include "../sass.hpp" -#include "allocator.hpp" -#include "memory_pool.hpp" - -#if defined (_MSC_VER) // Visual studio -#define thread_local __declspec( thread ) -#elif defined (__GCC__) // GCC -#define thread_local __thread -#endif - -namespace Sass { - -#ifdef SASS_CUSTOM_ALLOCATOR - - // Only use PODs for thread_local - // Objects get unpredictable init order - static thread_local MemoryPool* pool; - static thread_local size_t allocations; - - void* allocateMem(size_t size) - { - if (pool == nullptr) { - pool = new MemoryPool(); - } - allocations++; - return pool->allocate(size); - } - - void deallocateMem(void* ptr, size_t size) - { - - // It seems thread_local variable might be discharged!? - // But the destructors of e.g. static strings is still - // called, although their memory was discharged too. - // Fine with me as long as address sanitizer is happy. - if (pool == nullptr || allocations == 0) { return; } - - pool->deallocate(ptr); - if (--allocations == 0) { - delete pool; - pool = nullptr; - } - - } - -#endif - -} diff --git a/src/memory/config.hpp b/src/memory/config.hpp deleted file mode 100644 index 9d666b2e6b..0000000000 --- a/src/memory/config.hpp +++ /dev/null @@ -1,20 +0,0 @@ -#ifndef SASS_MEMORY_CONFIG_H -#define SASS_MEMORY_CONFIG_H - -// Define memory alignment requirements -#define SASS_MEM_ALIGN sizeof(unsigned int) - -// Minimal alignment for memory fragments. Must be a multiple -// of `SASS_MEM_ALIGN` and should not be too big (maybe 1 or 2) -#define SassAllocatorHeadSize sizeof(unsigned int) - -// The number of bytes we use for our book-keeping before every -// memory fragment. Needed to know to which bucket we belongs on -// deallocations, or if it should go directly to the `free` call. -#define SassAllocatorBookSize sizeof(unsigned int) - -// Bytes reserve for book-keeping on the arenas -// Currently unused and for later optimization -#define SassAllocatorArenaHeadSize 0 - -#endif \ No newline at end of file diff --git a/src/memory/shared_ptr.cpp b/src/memory/shared_ptr.cpp deleted file mode 100644 index faa5796fab..0000000000 --- a/src/memory/shared_ptr.cpp +++ /dev/null @@ -1,33 +0,0 @@ -#include "../sass.hpp" -#include -#include - -#include "shared_ptr.hpp" -#include "../ast_fwd_decl.hpp" - -#ifdef DEBUG_SHARED_PTR -#include "../debugger.hpp" -#endif - -namespace Sass { - - #ifdef DEBUG_SHARED_PTR - void SharedObj::dumpMemLeaks() { - if (!all.empty()) { - std::cerr << "###################################\n"; - std::cerr << "# REPORTING MISSING DEALLOCATIONS #\n"; - std::cerr << "###################################\n"; - for (SharedObj* var : all) { - if (AST_Node* ast = dynamic_cast(var)) { - debug_ast(ast); - } else { - std::cerr << "LEAKED " << var << "\n"; - } - } - } - } - sass::vector SharedObj::all; - #endif - - bool SharedObj::taint = false; -} diff --git a/src/memory/shared_ptr.hpp b/src/memory/shared_ptr.hpp deleted file mode 100644 index 09d263afb5..0000000000 --- a/src/memory/shared_ptr.hpp +++ /dev/null @@ -1,332 +0,0 @@ -#ifndef SASS_MEMORY_SHARED_PTR_H -#define SASS_MEMORY_SHARED_PTR_H - -#include "sass/base.h" - -#include "../sass.hpp" -#include "allocator.hpp" -#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 { - - // Forward declaration - class SharedPtr; - - /////////////////////////////////////////////////////////////////////////////// - // Use macros for the allocation task, since overloading operator `new` - // has been proven to be flaky under certain compilers (see comment below). - /////////////////////////////////////////////////////////////////////////////// - - #ifdef DEBUG_SHARED_PTR - - #define SASS_MEMORY_NEW(Class, ...) \ - ((Class*)(new Class(__VA_ARGS__))->trace(__FILE__, __LINE__)) \ - - #define SASS_MEMORY_COPY(obj) \ - ((obj)->copy(__FILE__, __LINE__)) \ - - #define SASS_MEMORY_CLONE(obj) \ - ((obj)->clone(__FILE__, __LINE__)) \ - - #else - - #define SASS_MEMORY_NEW(Class, ...) \ - new Class(__VA_ARGS__) \ - - #define SASS_MEMORY_COPY(obj) \ - ((obj)->copy()) \ - - #define SASS_MEMORY_CLONE(obj) \ - ((obj)->clone()) \ - - #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 { - 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 void dumpMemLeaks(); - SharedObj* trace(sass::string file, size_t line) { - this->file = file; - this->line = line; - return this; - } - sass::string getDbgFile() { return file; } - size_t getDbgLine() { return line; } - void setDbg(bool dbg) { this->dbg = dbg; } - size_t getRefCount() const { return refcount; } - #endif - - static void setTaint(bool val) { taint = val; } - - #ifdef SASS_CUSTOM_ALLOCATOR - inline void* operator new(size_t nbytes) { - return allocateMem(nbytes); - } - inline void operator delete(void* ptr) { - return deallocateMem(ptr); - } - #endif - - virtual sass::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 - sass::string file; - size_t line; - bool dbg = false; - static sass::vector all; - #endif - }; - - // SharedPtr is a intermediate (template-less) base class for SharedImpl. - // ToDo: there should be a way to include this in SharedImpl and to get - // ToDo: rid of all the static_cast that are now needed in SharedImpl. - class SharedPtr { - public: - 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; - } - 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"; - } - #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; - } - else if (node->refcount == 0) { - #ifdef DEBUG_SHARED_PTR - if (node->dbg) std::cerr << "NODE EVAEDED DELETE " << node << "\n"; - #endif - } - } - 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 { - - public: - SharedImpl() : SharedPtr(nullptr) {} - - template - SharedImpl(U* node) : - SharedPtr(static_cast(node)) {} - - template - SharedImpl(const SharedImpl& impl) : - SharedImpl(impl.ptr()) {} - - template - SharedImpl& operator=(U *rhs) { - return static_cast&>( - SharedPtr::operator=(static_cast(rhs))); - } - - template - SharedImpl& operator=(const SharedImpl& rhs) { - return static_cast&>( - SharedPtr::operator=(static_cast&>(rhs))); - } - - operator sass::string() const { - if (node) return node->to_string(); - return "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 diff --git a/src/memory_allocator.cpp b/src/memory_allocator.cpp new file mode 100644 index 0000000000..7f052f3be5 --- /dev/null +++ b/src/memory_allocator.cpp @@ -0,0 +1,57 @@ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#include "memory_allocator.hpp" + +#ifdef SASS_CUSTOM_ALLOCATOR +#include "memory_pool.hpp" + +namespace Sass { + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // You must only use PODs for thread_local. + // Objects get very unpredictable init order. + static thread_local MemoryPool* pool; + static thread_local size_t allocations; + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // Allocate memory from the memory pool. + // Memory pool is allocated on first call. + void* allocateMem(size_t size) + { + if (pool == nullptr) { + pool = new MemoryPool(); + } + ++allocations; + return pool->allocate(size); + } + + // Release the memory from the pool. + // Destroys the pool when it is emptied. + void deallocateMem(void* ptr, size_t size) + { + + // It seems thread_local variable might be discharged!? + // But the destructors of e.g. static strings is still + // called, although their memory was discharged too. + // Fine with me as long as address sanitizer is happy. + if (pool == nullptr || allocations == 0) { return; } + + pool->deallocate(ptr); + if (--allocations == 0) { + delete pool; + pool = nullptr; + } + + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + +} + +#endif diff --git a/src/memory/allocator.hpp b/src/memory_allocator.hpp similarity index 54% rename from src/memory/allocator.hpp rename to src/memory_allocator.hpp index a830457107..98abf4ad96 100644 --- a/src/memory/allocator.hpp +++ b/src/memory_allocator.hpp @@ -1,10 +1,15 @@ -#ifndef SASS_ALLOCATOR_H -#define SASS_ALLOCATOR_H - -#include "config.hpp" -#include "../settings.hpp" -#include "../MurmurHash2.hpp" - +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#ifndef SASS_ALLOCATOR_HPP +#define SASS_ALLOCATOR_HPP + +#include "memory_config.hpp" +#include "settings.hpp" +#include "MurmurHash2.hpp" +#include "randomize.hpp" + +#include #include #include #include @@ -13,14 +18,17 @@ namespace Sass { -#ifndef SASS_CUSTOM_ALLOCATOR - + // Fallback to standard allocator + #ifndef SASS_CUSTOM_ALLOCATOR template using Allocator = std::allocator; + #else -#else - + // Allocate memory from the memory pool. + // Memory pool is allocated on first call. void* allocateMem(size_t size); + // Release the memory from the pool. + // Destroys the pool when it is emptied. void deallocateMem(void* ptr, size_t size = 1); template @@ -38,16 +46,17 @@ namespace Sass { typedef std::size_t size_type; typedef std::ptrdiff_t difference_type; + // Not really sure what this does!? template struct rebind { typedef Allocator other; }; - // Constructor + // Default constructor Allocator(void) {} - // Copy Constructor + // Copy constructor template Allocator(Allocator const&) {} @@ -90,6 +99,7 @@ namespace Sass { }; + // Allocators are equal, don't care for type! template bool operator==(Allocator const& left, Allocator const& right) @@ -97,6 +107,7 @@ namespace Sass { return true; } + // Allocators are equal, don't care for type! template bool operator!=(Allocator const& left, Allocator const& right) @@ -104,31 +115,46 @@ namespace Sass { return !(left == right); } -#endif + // EO custom allocator + #endif - namespace sass { - template using vector = std::vector>; - using string = std::basic_string, Sass::Allocator>; - using sstream = std::basic_stringstream, Sass::Allocator>; - using ostream = std::basic_ostringstream, Sass::Allocator>; - using istream = std::basic_istringstream, Sass::Allocator>; - } +} +// Make them available on the global scope +// Easier for global structs needed for C linkage +namespace sass { // Note the lower-case notation +#ifndef SASS_CUSTOM_ALLOCATOR + template using deque = std::deque; + template using vector = std::vector; + using string = std::string; + using wstring = std::wstring; + using sstream = std::stringstream; + using ostream = std::ostringstream; + using istream = std::istringstream; +#else + template using deque = std::deque>; + template using vector = std::vector>; + using string = std::basic_string, Sass::Allocator>; + using wstring = std::basic_string, Sass::Allocator>; + using sstream = std::basic_stringstream, Sass::Allocator>; + using ostream = std::basic_ostringstream, Sass::Allocator>; + using istream = std::basic_istringstream, Sass::Allocator>; +#endif } #ifdef SASS_CUSTOM_ALLOCATOR namespace std { // Only GCC seems to need this specialization!? - template <> struct hash { + template <> struct hash { public: inline size_t operator()( - const Sass::sass::string& name) const + const sass::string& name) const { return MurmurHash2( (void*)name.c_str(), (int)name.size(), - 0x73617373); + Sass::getHashSeed()); } }; } diff --git a/src/memory_config.hpp b/src/memory_config.hpp new file mode 100644 index 0000000000..e23e873af4 --- /dev/null +++ b/src/memory_config.hpp @@ -0,0 +1,60 @@ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#ifndef SASS_MEMORY_CONFIG_H +#define SASS_MEMORY_CONFIG_H + +#include "settings.hpp" + +///////////////////////////////////////////////////////////////////////// +// Memory allocator configurations +///////////////////////////////////////////////////////////////////////// + +// Define memory alignment requirements +#define SASS_MEM_ALIGN sizeof(unsigned int) + +// The number of bytes we use for our book-keeping before every +// memory fragment. Needed to know to which bucket we belongs on +// deallocations, or if it should go directly to the `free` call. +#define SassAllocatorBookSize sizeof(unsigned int) + +// Bytes reserve for book-keeping on the arenas +// Currently unused and for later optimization +#define SassAllocatorArenaHeadSize 0 + +///////////////////////////////////////////////////////////////////////// +// Below settings should only be changed if you know what you do! +///////////////////////////////////////////////////////////////////////// + +// Detail settings for pool allocator +#ifdef SASS_CUSTOM_ALLOCATOR + + // How many buckets should we have for the free-list + // We have a bucket for every `SASS_MEM_ALIGN` * `SassAllocatorBuckets` + // When something requests x amount of memory, we will pad the request + // to be a multiple of `SASS_MEM_ALIGN` and then assign it either to + // an existing bucket or directly use to malloc/free. Otherwise we will + // chunk out a slice of the arena to store it in that memory. + #define SassAllocatorBuckets 960 + + // The size of the memory pool arenas in bytes. + // This determines the minimum allocated memory chunk. + // Whenever we need more memory, we malloc that much. + #define SassAllocatorArenaSize (1024 * 1024) + +#endif +// EO SASS_CUSTOM_ALLOCATOR + +///////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////// + +#ifdef _MSC_VER +#ifdef DEBUG_SHARED_PTR +#define DEBUG_MSVC_CRT_MEM +#endif +#endif + +///////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////// + +#endif diff --git a/src/memory/memory_pool.hpp b/src/memory_pool.hpp similarity index 86% rename from src/memory/memory_pool.hpp rename to src/memory_pool.hpp index d2cef9930d..a392ce3ad8 100644 --- a/src/memory/memory_pool.hpp +++ b/src/memory_pool.hpp @@ -1,15 +1,23 @@ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ #ifndef SASS_MEMORY_POOL_H #define SASS_MEMORY_POOL_H -#include -#include -#include -#include #include +#include +#include +#include + +#include "memory_config.hpp" namespace Sass { +#ifdef SASS_CUSTOM_ALLOCATOR + + ///////////////////////////////////////////////////////////////////////// // SIMPLE MEMORY-POOL ALLOCATOR WITH FREE-LIST ON TOP + ///////////////////////////////////////////////////////////////////////// // This is a memory pool allocator with a free list on top. // We only allocate memory arenas from the system in specific @@ -30,10 +38,13 @@ namespace Sass { // https://en.wikipedia.org/wiki/Free_list // On allocation calls we first check if there is any suitable - // item on the free-list. If there is we pop it from the stack + // item on the free-list. If there is, we pop it from the stack // and return it to the caller. Otherwise we have to take out // a new slice from the current `arena` and increase `offset`. + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + // Note that this is not thread safe. This is on purpose as we // want to use the memory pool in a thread local usage. In order // to get this thread safe you need to only allocate one pool @@ -43,6 +54,9 @@ namespace Sass { // static thread_local size_t allocations; // static thread_local MemoryPool* pool; + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + class MemoryPool { // Current arena we fill up @@ -75,8 +89,7 @@ namespace Sass { // Set to maximum value in order to // make an allocation on the first run offset(std::string::npos) - { - } + {} // Destructor ~MemoryPool() { @@ -86,7 +99,6 @@ namespace Sass { } // Delete current arena free(arena); - } // Allocate a slice of the memory pool @@ -154,6 +166,7 @@ namespace Sass { } // EO allocate + // Deallocate the pointer void deallocate(void* ptr) { @@ -181,6 +194,8 @@ namespace Sass { }; +#endif + } #endif diff --git a/src/modules.cpp b/src/modules.cpp new file mode 100644 index 0000000000..b94651ec1b --- /dev/null +++ b/src/modules.cpp @@ -0,0 +1,77 @@ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#include "modules.hpp" + +#include "stylesheet.hpp" + +namespace Sass { + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + Module::Module(EnvRefs* idxs) : + Env(idxs), + extender() + {} + + // Check if there are any unsatisfied extends (will throw) + + bool Module::checkForUnsatisfiedExtends3(Extension & unsatisfied) const + { + ExtSmplSelSet originals; + for (auto& entry : extender->selectors54) { + originals.insert(entry.first); + } + + if (extender->checkForUnsatisfiedExtends2(unsatisfied)) { + return true; + } + for (auto& asd : upstream) { + if (asd->checkForUnsatisfiedExtends3(unsatisfied)) { + return true; + } + } + return false; + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + BuiltInMod::BuiltInMod(EnvRoot& root) : + Module(new EnvRefs( + root, + nullptr, + false, // isImport + true, // isInternal + false)) // isSemiGlobal + { + isBuiltIn = true; + isLoaded = true; + isCompiled = true; + } + + BuiltInMod::~BuiltInMod() + { + delete idxs; + } + + void BuiltInMod::addFunction(const EnvKey& name, uint32_t offset) + { + idxs->fnIdxs[name] = offset; + } + + void BuiltInMod::addVariable(const EnvKey& name, uint32_t offset) + { + idxs->varIdxs[name] = offset; + } + + void BuiltInMod::addMixin(const EnvKey& name, uint32_t offset) + { + idxs->mixIdxs[name] = offset; + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + +} diff --git a/src/modules.hpp b/src/modules.hpp new file mode 100644 index 0000000000..b2b6eda35f --- /dev/null +++ b/src/modules.hpp @@ -0,0 +1,111 @@ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#ifndef SASS_MODULES_HPP +#define SASS_MODULES_HPP + +// sass.hpp must go before all system headers +// to get the __EXTENSIONS__ fix on Solaris. +#include "capi_sass.hpp" + +#include "ast_fwd_decl.hpp" +#include "environment_cnt.hpp" +#include "environment_stack.hpp" + +namespace Sass { + + ///////////////////////////////////////////////////////////////////////// + // A module is first and foremost a unit that provides variables, + // functions and mixins. We know built-in modules, which don't have + // any content ever and are always loaded and available. Custom + // modules are loaded from any given url. They are related and + // linked to regular @imports, as those are also some kind of + // special modules. An @import will load as a module, but that + // module with not be compiled until used by @forward or @use. + ///////////////////////////////////////////////////////////////////////// + // When a module is @imported, all its local root variables + // are "exposed" to the caller's scope. Those are actual new + // instances of the variables, as e.g. the same file might be + // imported into different style-rules. The variables will then + // really exist in the env of those style-rules. Similarly when + // importing on the root scope, the variables are not shared with + // the internal ones (the ones we reference when being @used). + ///////////////////////////////////////////////////////////////////////// + // To support this, we can't statically optimize variable accesses + // across module boundaries. We need to mark environments to remember + // in which context a module was brought into the tree. For imports + // we simply skip the whole frame. Instead the lookup should find + // the variable we created in the caller's scope (Optimizations ToDo). + ///////////////////////////////////////////////////////////////////////// + + class Module : public Env + { + public: + + // Flag for internal modules + // They don't have any content + bool isBuiltIn = false; + + // Only makes sense for non built-ins + // True once the content has been loaded + bool isLoaded = false; + + // Only makes sense for non built-ins + // True once the module is compiled and ready + bool isCompiled = false; + + // The compiled AST-Tree + CssParentNodeObj compiled; + + // All @forward rules get merged into these objects + // Those are not available on the local scope, they + // are only used when another module consumes us! + // On @use they must be merged into local scope! + VidxEnvKeyMap mergedFwdVar; + MidxEnvKeyMap mergedFwdMix; + FidxEnvKeyMap mergedFwdFn; + + // Modules that this module uses. + sass::vector upstream; + + ModuleMap> moduse; + + // The extensions defined in this module, which is also able to update + // [css]'s style rules in-place based on downstream extensions. + ExtensionStoreObj extender = nullptr; + + // Special set with global assignments + // Needed for imports within style-rules + // ToDo: not really tested via specs yet? + // EnvKeySet globals; + + public: + + Module(EnvRefs* idxs); + + // Check if there are any unsatisfied extends (will throw) + bool checkForUnsatisfiedExtends3(Extension& unsatisfied) const; + + }; + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + class BuiltInMod : public Module + { + public: + void addFunction(const EnvKey& name, uint32_t offset); + void addVariable(const EnvKey& name, uint32_t offset); + void addMixin(const EnvKey& name, uint32_t offset); + BuiltInMod(EnvRoot& root); + ~BuiltInMod(); + }; + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + +} + +#include "extender.hpp" + +#endif diff --git a/src/offset.cpp b/src/offset.cpp new file mode 100644 index 0000000000..fd029acdb9 --- /dev/null +++ b/src/offset.cpp @@ -0,0 +1,170 @@ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#include "offset.hpp" + +#include "charcode.hpp" +#include "character.hpp" + +namespace Sass { + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + using namespace Charcode; + using namespace Character; + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // Default constructor + Offset::Offset() {} + + // Create an Offset from the given string + // Will use `plus` internally on all chars + Offset::Offset(uint8_t character) + { + plus(character); + } + + // Create an Offset from the given string + // Will use `plus` internally on all chars + Offset::Offset(const sass::string& text) + { + for (uint8_t character : text) { + plus(character); + } + } + + // Create an Offset from the given char star + // Will use `plus` internally on all chars + Offset::Offset(const char* text, const char* end) + { + if (end == nullptr) { + while (*text != 0) { + plus(*text++); + } + } + else { + while (text < end) { + plus(*text++); + } + } + } + + // Append [character] to increment offset + void Offset::plus(uint8_t character) + { + switch (character) { + case $lf: + line += 1; + column = 0; + break; + case $space: + case $tab: + case $vt: + case $ff: + case $cr: + column += 1; + break; + default: + // skip over 10xxxxxx and 01xxxxxx + // count ASCII and initial utf8 bytes + if (Character::isCharacter(character)) { + // 64 => initial utf8 byte + // 128 => regular ASCII char + column += 1; + } + break; + } + } + + // Append [text] to increment offset + void Offset::plus(const sass::string& text) + { + for (uint8_t character : text) { + plus(character); + } + } + + // Create offset with given [line] and [column] + // Needs static constructor to avoid ambiguity + Offset Offset::init(size_t line, size_t column) + { + Offset offset; + offset.line = line == NPOS ? -1 : (uint32_t) line; + offset.column = column == NPOS ? -1 : (uint32_t) column; + return offset; + } + // EO Offset::init + + // Return the `distance` between [start[ and [end] + // Gives the solution to the equation `end = start + x` + Offset Offset::distance(const Offset& start, const Offset& end) + { + Offset rv(end); + // Both are on the same line + if (start.line == end.line) { + // Get distance between columns + rv.column -= start.column; + // Line distance is zero + rv.line = 0; + } + else { + // Get distance between lines + rv.line -= start.line; + // Columns don't need to be changed + // Since we land on another line, we + // will reach the same end column + } + return rv; + } + // EO Offset::distance + + // Implement equality operators + bool Offset::operator== (const Offset& rhs) const + { + return line == rhs.line + && column == rhs.column; + } + // EO Offset::operator== + + // Implement assign and addition operator + void Offset::operator+= (const Offset& rhs) + { + // lines are always summed up + line += rhs.line; + // columns may need to be reset + if (rhs.line == 0) { + column += rhs.column; + } + else { + column = rhs.column; + } + } + // EO Offset::operator+= + + // Implement addition operator (returns new Offset) + Offset Offset::operator+ (const Offset& rhs) const + { + Offset rv(*this); + rv += rhs; + return rv; + } + // EO Offset::operator+ + + // Implement multiply operator (returns new Offset) + Offset Offset::operator* (uint32_t mul) const + { + Offset rv(*this); + if (rv.line == 0) { + rv.column *= mul; + } + else { + rv.line *= mul; + } + return rv; + } + // EO Offset::operator* + +} diff --git a/src/offset.hpp b/src/offset.hpp new file mode 100644 index 0000000000..f1706549c0 --- /dev/null +++ b/src/offset.hpp @@ -0,0 +1,86 @@ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#ifndef SASS_OFFSET_HPP +#define SASS_OFFSET_HPP + +// sass.hpp must go before all system headers +// to get the __EXTENSIONS__ fix on Solaris. +#include "capi_sass.hpp" + +#include "ast_def_macros.hpp" + +namespace Sass { + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // Basic class for text file positions + // The logic how to count characters and + // to add/subtract are implemented here. + class Offset + { + public: + + // All properties are public (zero based) + // Getters return human-readable form (+1) + uint32_t line = 0; + uint32_t column = 0; + + // Default constructor + Offset(); + + // Create an Offset from the given string + // Will use `plus` internally on all chars + Offset(uint8_t character); + + // Create an Offset from the given string + // Will use `plus` internally on all chars + Offset(const sass::string& text); + + // Create an Offset from the given char star + // Will use `plus` internally on all chars + Offset(const char* beg, const char* end = 0); + + // Append [character] to increment offset + void plus(uint8_t character); + + // Append [text] to increment offset + void plus(const sass::string& text); + + // Create offset with given [line] and [column] + // Needs static constructor to avoid ambiguity + static Offset init(size_t line, size_t column); + + // Return the `distance` between [start[ and [end] + // Gives the solution to the equation `end = start + x` + static Offset distance(const Offset& start, const Offset& end); + + // Assign and increment operator + void operator+= (const Offset& pos); + + // Plus operator (returns new Offset) + Offset operator+ (const Offset& off) const; + + // Multiply operator (returns new Offset) + Offset operator* (uint32_t mul) const; + + // Implement equal and derive unequal + bool operator==(const Offset& rhs) const; + + // Delete other operators to make implementation more clear + // Helps us spot cases where we use undefined implementations + // bool operator!=(const Offset& rhs) const = delete; + // bool operator>=(const Offset& rhs) const = delete; + // bool operator<=(const Offset& rhs) const = delete; + // bool operator>(const Offset& rhs) const = delete; + // bool operator<(const Offset& rhs) const = delete; + + }; + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + +} + +#endif diff --git a/src/operation.hpp b/src/operation.hpp deleted file mode 100644 index fafd2c73be..0000000000 --- a/src/operation.hpp +++ /dev/null @@ -1,223 +0,0 @@ -#ifndef SASS_OPERATION_H -#define SASS_OPERATION_H - -// sass.hpp must go before all system headers to get the -// __EXTENSIONS__ fix on Solaris. -#include "sass.hpp" - -// base classes to implement curiously recurring template pattern (CRTP) -// https://en.wikipedia.org/wiki/Curiously_recurring_template_pattern - -#include -#include - -#include "ast_fwd_decl.hpp" -#include "ast_def_macros.hpp" - -namespace Sass { - - #define ATTACH_ABSTRACT_CRTP_PERFORM_METHODS()\ - virtual void perform(Operation* op) = 0; \ - virtual Value* perform(Operation* op) = 0; \ - virtual sass::string perform(Operation* op) = 0; \ - virtual AST_Node* perform(Operation* op) = 0; \ - virtual Selector* perform(Operation* op) = 0; \ - virtual Statement* perform(Operation* op) = 0; \ - virtual Expression* perform(Operation* op) = 0; \ - virtual union Sass_Value* perform(Operation* op) = 0; \ - virtual SupportsCondition* perform(Operation* op) = 0; \ - - // you must add operators to every class - // ensures `this` of actual instance type - // we therefore call the specific operator - // they are virtual so most specific is used - #define ATTACH_CRTP_PERFORM_METHODS()\ - virtual void perform(Operation* op) override { return (*op)(this); } \ - virtual Value* perform(Operation* op) override { return (*op)(this); } \ - virtual sass::string perform(Operation* op) override { return (*op)(this); } \ - virtual AST_Node* perform(Operation* op) override { return (*op)(this); } \ - virtual Selector* perform(Operation* op) override { return (*op)(this); } \ - virtual Statement* perform(Operation* op) override { return (*op)(this); } \ - virtual Expression* perform(Operation* op) override { return (*op)(this); } \ - virtual union Sass_Value* perform(Operation* op) override { return (*op)(this); } \ - virtual SupportsCondition* perform(Operation* op) override { return (*op)(this); } \ - - template - class Operation { - public: - virtual T operator()(AST_Node* x) = 0; - // statements - virtual T operator()(Block* x) = 0; - virtual T operator()(StyleRule* x) = 0; - virtual T operator()(Bubble* x) = 0; - virtual T operator()(Trace* x) = 0; - virtual T operator()(SupportsRule* x) = 0; - virtual T operator()(MediaRule* x) = 0; - virtual T operator()(CssMediaRule* x) = 0; - virtual T operator()(CssMediaQuery* x) = 0; - virtual T operator()(AtRootRule* x) = 0; - virtual T operator()(AtRule* x) = 0; - virtual T operator()(Keyframe_Rule* x) = 0; - virtual T operator()(Declaration* x) = 0; - virtual T operator()(Assignment* x) = 0; - virtual T operator()(Import* x) = 0; - virtual T operator()(Import_Stub* x) = 0; - virtual T operator()(WarningRule* x) = 0; - virtual T operator()(ErrorRule* x) = 0; - virtual T operator()(DebugRule* x) = 0; - virtual T operator()(Comment* x) = 0; - virtual T operator()(If* x) = 0; - virtual T operator()(ForRule* x) = 0; - virtual T operator()(EachRule* x) = 0; - virtual T operator()(WhileRule* x) = 0; - virtual T operator()(Return* x) = 0; - virtual T operator()(Content* x) = 0; - virtual T operator()(ExtendRule* x) = 0; - virtual T operator()(Definition* x) = 0; - virtual T operator()(Mixin_Call* x) = 0; - // expressions - virtual T operator()(Null* x) = 0; - virtual T operator()(List* x) = 0; - virtual T operator()(Map* x) = 0; - virtual T operator()(Function* x) = 0; - virtual T operator()(Binary_Expression* x) = 0; - virtual T operator()(Unary_Expression* x) = 0; - virtual T operator()(Function_Call* x) = 0; - virtual T operator()(Custom_Warning* x) = 0; - virtual T operator()(Custom_Error* x) = 0; - virtual T operator()(Variable* x) = 0; - virtual T operator()(Number* x) = 0; - virtual T operator()(Color* x) = 0; - virtual T operator()(Color_RGBA* x) = 0; - virtual T operator()(Color_HSLA* x) = 0; - virtual T operator()(Boolean* x) = 0; - virtual T operator()(String_Schema* x) = 0; - virtual T operator()(String_Quoted* x) = 0; - virtual T operator()(String_Constant* x) = 0; - virtual T operator()(SupportsCondition* x) = 0; - virtual T operator()(SupportsOperation* x) = 0; - virtual T operator()(SupportsNegation* x) = 0; - virtual T operator()(SupportsDeclaration* x) = 0; - virtual T operator()(Supports_Interpolation* x) = 0; - virtual T operator()(Media_Query* x) = 0; - virtual T operator()(Media_Query_Expression* x) = 0; - virtual T operator()(At_Root_Query* x) = 0; - virtual T operator()(Parent_Reference* x) = 0; - // parameters and arguments - virtual T operator()(Parameter* x) = 0; - virtual T operator()(Parameters* x) = 0; - virtual T operator()(Argument* x) = 0; - virtual T operator()(Arguments* x) = 0; - // selectors - virtual T operator()(Selector_Schema* x) = 0; - virtual T operator()(PlaceholderSelector* x) = 0; - virtual T operator()(TypeSelector* x) = 0; - virtual T operator()(ClassSelector* x) = 0; - virtual T operator()(IDSelector* x) = 0; - virtual T operator()(AttributeSelector* x) = 0; - virtual T operator()(PseudoSelector* x) = 0; - virtual T operator()(SelectorComponent* x) = 0; - virtual T operator()(SelectorCombinator* x) = 0; - virtual T operator()(CompoundSelector* x) = 0; - virtual T operator()(ComplexSelector* x) = 0; - virtual T operator()(SelectorList* x) = 0; - - }; - - // example: Operation_CRTP - // T is the base return type of all visitors - // D is the class derived visitor class - // normally you want to implement all operators - template - class Operation_CRTP : public Operation { - public: - T operator()(AST_Node* x) { return static_cast(this)->fallback(x); } - // statements - T operator()(Block* x) { return static_cast(this)->fallback(x); } - T operator()(StyleRule* x) { return static_cast(this)->fallback(x); } - T operator()(Bubble* x) { return static_cast(this)->fallback(x); } - T operator()(Trace* x) { return static_cast(this)->fallback(x); } - T operator()(SupportsRule* x) { return static_cast(this)->fallback(x); } - T operator()(MediaRule* x) { return static_cast(this)->fallback(x); } - T operator()(CssMediaRule* x) { return static_cast(this)->fallback(x); } - T operator()(CssMediaQuery* x) { return static_cast(this)->fallback(x); } - T operator()(AtRootRule* x) { return static_cast(this)->fallback(x); } - T operator()(AtRule* x) { return static_cast(this)->fallback(x); } - T operator()(Keyframe_Rule* x) { return static_cast(this)->fallback(x); } - T operator()(Declaration* x) { return static_cast(this)->fallback(x); } - T operator()(Assignment* x) { return static_cast(this)->fallback(x); } - T operator()(Import* x) { return static_cast(this)->fallback(x); } - T operator()(Import_Stub* x) { return static_cast(this)->fallback(x); } - T operator()(WarningRule* x) { return static_cast(this)->fallback(x); } - T operator()(ErrorRule* x) { return static_cast(this)->fallback(x); } - T operator()(DebugRule* x) { return static_cast(this)->fallback(x); } - T operator()(Comment* x) { return static_cast(this)->fallback(x); } - T operator()(If* x) { return static_cast(this)->fallback(x); } - T operator()(ForRule* x) { return static_cast(this)->fallback(x); } - T operator()(EachRule* x) { return static_cast(this)->fallback(x); } - T operator()(WhileRule* x) { return static_cast(this)->fallback(x); } - T operator()(Return* x) { return static_cast(this)->fallback(x); } - T operator()(Content* x) { return static_cast(this)->fallback(x); } - T operator()(ExtendRule* x) { return static_cast(this)->fallback(x); } - T operator()(Definition* x) { return static_cast(this)->fallback(x); } - T operator()(Mixin_Call* x) { return static_cast(this)->fallback(x); } - // expressions - T operator()(Null* x) { return static_cast(this)->fallback(x); } - T operator()(List* x) { return static_cast(this)->fallback(x); } - T operator()(Map* x) { return static_cast(this)->fallback(x); } - T operator()(Function* x) { return static_cast(this)->fallback(x); } - T operator()(Binary_Expression* x) { return static_cast(this)->fallback(x); } - T operator()(Unary_Expression* x) { return static_cast(this)->fallback(x); } - T operator()(Function_Call* x) { return static_cast(this)->fallback(x); } - T operator()(Custom_Warning* x) { return static_cast(this)->fallback(x); } - T operator()(Custom_Error* x) { return static_cast(this)->fallback(x); } - T operator()(Variable* x) { return static_cast(this)->fallback(x); } - T operator()(Number* x) { return static_cast(this)->fallback(x); } - T operator()(Color* x) { return static_cast(this)->fallback(x); } - T operator()(Color_RGBA* x) { return static_cast(this)->fallback(x); } - T operator()(Color_HSLA* x) { return static_cast(this)->fallback(x); } - T operator()(Boolean* x) { return static_cast(this)->fallback(x); } - T operator()(String_Schema* x) { return static_cast(this)->fallback(x); } - T operator()(String_Constant* x) { return static_cast(this)->fallback(x); } - T operator()(String_Quoted* x) { return static_cast(this)->fallback(x); } - T operator()(SupportsCondition* x) { return static_cast(this)->fallback(x); } - T operator()(SupportsOperation* x) { return static_cast(this)->fallback(x); } - T operator()(SupportsNegation* x) { return static_cast(this)->fallback(x); } - T operator()(SupportsDeclaration* x) { return static_cast(this)->fallback(x); } - T operator()(Supports_Interpolation* x) { return static_cast(this)->fallback(x); } - T operator()(Media_Query* x) { return static_cast(this)->fallback(x); } - T operator()(Media_Query_Expression* x) { return static_cast(this)->fallback(x); } - T operator()(At_Root_Query* x) { return static_cast(this)->fallback(x); } - T operator()(Parent_Reference* x) { return static_cast(this)->fallback(x); } - // parameters and arguments - T operator()(Parameter* x) { return static_cast(this)->fallback(x); } - T operator()(Parameters* x) { return static_cast(this)->fallback(x); } - T operator()(Argument* x) { return static_cast(this)->fallback(x); } - T operator()(Arguments* x) { return static_cast(this)->fallback(x); } - // selectors - T operator()(Selector_Schema* x) { return static_cast(this)->fallback(x); } - T operator()(PlaceholderSelector* x) { return static_cast(this)->fallback(x); } - T operator()(TypeSelector* x) { return static_cast(this)->fallback(x); } - T operator()(ClassSelector* x) { return static_cast(this)->fallback(x); } - T operator()(IDSelector* x) { return static_cast(this)->fallback(x); } - T operator()(AttributeSelector* x) { return static_cast(this)->fallback(x); } - T operator()(PseudoSelector* x) { return static_cast(this)->fallback(x); } - T operator()(SelectorComponent* x) { return static_cast(this)->fallback(x); } - T operator()(SelectorCombinator* x) { return static_cast(this)->fallback(x); } - T operator()(CompoundSelector* x) { return static_cast(this)->fallback(x); } - T operator()(ComplexSelector* x) { return static_cast(this)->fallback(x); } - T operator()(SelectorList* x) { return static_cast(this)->fallback(x); } - - // fallback with specific type U - // will be called if not overloaded - template inline T fallback(U x) - { - throw std::runtime_error( - std::string(typeid(*this).name()) + ": CRTP not implemented for " + typeid(x).name()); - } - - }; - -} - -#endif diff --git a/src/operators.cpp b/src/operators.cpp deleted file mode 100644 index a76bce3c48..0000000000 --- a/src/operators.cpp +++ /dev/null @@ -1,267 +0,0 @@ -// sass.hpp must go before all system headers to get the -// __EXTENSIONS__ fix on Solaris. -#include "sass.hpp" - -#include -#include "operators.hpp" - -namespace Sass { - - namespace Operators { - - inline double add(double x, double y) { return x + y; } - inline double sub(double x, double y) { return x - y; } - inline double mul(double x, double y) { return x * y; } - inline double div(double x, double y) { return x / y; } // x/0 checked by caller - - inline double mod(double x, double y) { // x/0 checked by caller - if ((x > 0 && y < 0) || (x < 0 && y > 0)) { - double ret = std::fmod(x, y); - return ret ? ret + y : ret; - } else { - return std::fmod(x, y); - } - } - - typedef double (*bop)(double, double); - bop ops[Sass_OP::NUM_OPS] = { - 0, 0, // and, or - 0, 0, 0, 0, 0, 0, // eq, neq, gt, gte, lt, lte - add, sub, mul, div, mod - }; - - /* static function, has no pstate or traces */ - bool eq(ExpressionObj lhs, ExpressionObj rhs) - { - // operation is undefined if one is not a number - if (!lhs || !rhs) throw Exception::UndefinedOperation(lhs, rhs, Sass_OP::EQ); - // use compare operator from ast node - return *lhs == *rhs; - } - - /* static function, throws OperationError, has no pstate or traces */ - bool cmp(ExpressionObj lhs, ExpressionObj rhs, const Sass_OP op) - { - // can only compare numbers!? - Number_Obj l = Cast(lhs); - Number_Obj r = Cast(rhs); - // operation is undefined if one is not a number - if (!l || !r) throw Exception::UndefinedOperation(lhs, rhs, op); - // use compare operator from ast node - return *l < *r; - } - - /* static functions, throws OperationError, has no pstate or traces */ - bool lt(ExpressionObj lhs, ExpressionObj rhs) { return cmp(lhs, rhs, Sass_OP::LT); } - bool neq(ExpressionObj lhs, ExpressionObj rhs) { return eq(lhs, rhs) == false; } - bool gt(ExpressionObj lhs, ExpressionObj rhs) { return !cmp(lhs, rhs, Sass_OP::GT) && neq(lhs, rhs); } - bool lte(ExpressionObj lhs, ExpressionObj rhs) { return cmp(lhs, rhs, Sass_OP::LTE) || eq(lhs, rhs); } - bool gte(ExpressionObj lhs, ExpressionObj rhs) { return !cmp(lhs, rhs, Sass_OP::GTE) || eq(lhs, rhs); } - - /* colour math deprecation warning */ - void op_color_deprecation(enum Sass_OP op, sass::string lsh, sass::string rhs, const SourceSpan& pstate) - { - deprecated( - "The operation `" + lsh + " " + sass_op_to_name(op) + " " + rhs + - "` is deprecated and will be an error in future versions.", - "Consider using Sass's color functions instead.\n" - "https://sass-lang.com/documentation/Sass/Script/Functions.html#other_color_functions", - /*with_column=*/false, pstate); - } - - /* static function, throws OperationError, has no traces but optional pstate for returned value */ - Value* op_strings(Sass::Operand operand, Value& lhs, Value& rhs, struct Sass_Inspect_Options opt, const SourceSpan& pstate, bool delayed) - { - enum Sass_OP op = operand.operand; - - String_Quoted* lqstr = Cast(&lhs); - String_Quoted* rqstr = Cast(&rhs); - - sass::string lstr(lqstr ? lqstr->value() : lhs.to_string(opt)); - sass::string rstr(rqstr ? rqstr->value() : rhs.to_string(opt)); - - if (Cast(&lhs)) throw Exception::InvalidNullOperation(&lhs, &rhs, op); - if (Cast(&rhs)) throw Exception::InvalidNullOperation(&lhs, &rhs, op); - - sass::string sep; - switch (op) { - case Sass_OP::ADD: sep = ""; break; - case Sass_OP::SUB: sep = "-"; break; - case Sass_OP::DIV: sep = "/"; break; - case Sass_OP::EQ: sep = "=="; break; - case Sass_OP::NEQ: sep = "!="; break; - case Sass_OP::LT: sep = "<"; break; - case Sass_OP::GT: sep = ">"; break; - case Sass_OP::LTE: sep = "<="; break; - case Sass_OP::GTE: sep = ">="; break; - default: - throw Exception::UndefinedOperation(&lhs, &rhs, op); - break; - } - - if (op == Sass_OP::ADD) { - // create string that might be quoted on output (but do not unquote what we pass) - return SASS_MEMORY_NEW(String_Quoted, pstate, lstr + rstr, 0, false, true); - } - - // add whitespace around operator - // but only if result is not delayed - if (sep != "" && delayed == false) { - if (operand.ws_before) sep = " " + sep; - if (operand.ws_after) sep = sep + " "; - } - - if (op == Sass_OP::SUB || op == Sass_OP::DIV) { - if (lqstr && lqstr->quote_mark()) lstr = quote(lstr); - if (rqstr && rqstr->quote_mark()) rstr = quote(rstr); - } - - return SASS_MEMORY_NEW(String_Constant, pstate, lstr + sep + rstr); - } - - /* ToDo: allow to operate also with hsla colors */ - /* static function, throws OperationError, has no traces but optional pstate for returned value */ - Value* op_colors(enum Sass_OP op, const Color_RGBA& lhs, const Color_RGBA& rhs, struct Sass_Inspect_Options opt, const SourceSpan& pstate, bool delayed) - { - - if (lhs.a() != rhs.a()) { - throw Exception::AlphaChannelsNotEqual(&lhs, &rhs, op); - } - if ((op == Sass_OP::DIV || op == Sass_OP::MOD) && (!rhs.r() || !rhs.g() || !rhs.b())) { - throw Exception::ZeroDivisionError(lhs, rhs); - } - - op_color_deprecation(op, lhs.to_string(), rhs.to_string(), pstate); - - return SASS_MEMORY_NEW(Color_RGBA, - pstate, - ops[op](lhs.r(), rhs.r()), - ops[op](lhs.g(), rhs.g()), - ops[op](lhs.b(), rhs.b()), - lhs.a()); - } - - /* static function, throws OperationError, has no traces but optional pstate for returned value */ - Value* op_numbers(enum Sass_OP op, const Number& lhs, const Number& rhs, struct Sass_Inspect_Options opt, const SourceSpan& pstate, bool delayed) - { - double lval = lhs.value(); - double rval = rhs.value(); - - if (op == Sass_OP::MOD && rval == 0) { - return SASS_MEMORY_NEW(String_Quoted, pstate, "NaN"); - } - - if (op == Sass_OP::DIV && rval == 0) { - sass::string result(lval ? "Infinity" : "NaN"); - return SASS_MEMORY_NEW(String_Quoted, pstate, result); - } - - size_t l_n_units = lhs.numerators.size(); - size_t l_d_units = lhs.numerators.size(); - size_t r_n_units = rhs.denominators.size(); - size_t r_d_units = rhs.denominators.size(); - // optimize out the most common and simplest case - if (l_n_units == r_n_units && l_d_units == r_d_units) { - if (l_n_units + l_d_units <= 1 && r_n_units + r_d_units <= 1) { - if (lhs.numerators == rhs.numerators) { - if (lhs.denominators == rhs.denominators) { - Number* v = SASS_MEMORY_COPY(&lhs); - v->value(ops[op](lval, rval)); - return v; - } - } - } - } - - Number_Obj v = SASS_MEMORY_COPY(&lhs); - - if (lhs.is_unitless() && (op == Sass_OP::ADD || op == Sass_OP::SUB || op == Sass_OP::MOD)) { - v->numerators = rhs.numerators; - v->denominators = rhs.denominators; - } - - if (op == Sass_OP::MUL) { - v->value(ops[op](lval, rval)); - v->numerators.insert(v->numerators.end(), - rhs.numerators.begin(), rhs.numerators.end() - ); - v->denominators.insert(v->denominators.end(), - rhs.denominators.begin(), rhs.denominators.end() - ); - v->reduce(); - } - else if (op == Sass_OP::DIV) { - v->value(ops[op](lval, rval)); - v->numerators.insert(v->numerators.end(), - rhs.denominators.begin(), rhs.denominators.end() - ); - v->denominators.insert(v->denominators.end(), - rhs.numerators.begin(), rhs.numerators.end() - ); - v->reduce(); - } - else { - Number ln(lhs), rn(rhs); - ln.reduce(); rn.reduce(); - double f(rn.convert_factor(ln)); - v->value(ops[op](lval, rn.value() * f)); - } - - v->pstate(pstate); - return v.detach(); - } - - /* static function, throws OperationError, has no traces but optional pstate for returned value */ - Value* op_number_color(enum Sass_OP op, const Number& lhs, const Color_RGBA& rhs, struct Sass_Inspect_Options opt, const SourceSpan& pstate, bool delayed) - { - double lval = lhs.value(); - - switch (op) { - case Sass_OP::ADD: - case Sass_OP::MUL: { - op_color_deprecation(op, lhs.to_string(), rhs.to_string(opt), pstate); - return SASS_MEMORY_NEW(Color_RGBA, - pstate, - ops[op](lval, rhs.r()), - ops[op](lval, rhs.g()), - ops[op](lval, rhs.b()), - rhs.a()); - } - case Sass_OP::SUB: - case Sass_OP::DIV: { - sass::string color(rhs.to_string(opt)); - op_color_deprecation(op, lhs.to_string(), color, pstate); - return SASS_MEMORY_NEW(String_Quoted, - pstate, - lhs.to_string(opt) - + sass_op_separator(op) - + color); - } - default: break; - } - throw Exception::UndefinedOperation(&lhs, &rhs, op); - } - - /* static function, throws OperationError, has no traces but optional pstate for returned value */ - Value* op_color_number(enum Sass_OP op, const Color_RGBA& lhs, const Number& rhs, struct Sass_Inspect_Options opt, const SourceSpan& pstate, bool delayed) - { - double rval = rhs.value(); - - if ((op == Sass_OP::DIV || op == Sass_OP::MOD) && rval == 0) { - // comparison of Fixnum with Float failed? - throw Exception::ZeroDivisionError(lhs, rhs); - } - - op_color_deprecation(op, lhs.to_string(), rhs.to_string(), pstate); - - return SASS_MEMORY_NEW(Color_RGBA, - pstate, - ops[op](lhs.r(), rval), - ops[op](lhs.g(), rval), - ops[op](lhs.b(), rval), - lhs.a()); - } - - } - -} diff --git a/src/operators.hpp b/src/operators.hpp deleted file mode 100644 index 42616320e5..0000000000 --- a/src/operators.hpp +++ /dev/null @@ -1,30 +0,0 @@ -#ifndef SASS_OPERATORS_H -#define SASS_OPERATORS_H - -#include "values.hpp" -#include "sass/values.h" - -namespace Sass { - - namespace Operators { - - // equality operator using AST Node operator== - bool eq(ExpressionObj, ExpressionObj); - bool neq(ExpressionObj, ExpressionObj); - // specific operators based on cmp and eq - bool lt(ExpressionObj, ExpressionObj); - bool gt(ExpressionObj, ExpressionObj); - bool lte(ExpressionObj, ExpressionObj); - bool gte(ExpressionObj, ExpressionObj); - // arithmetic for all the combinations that matter - Value* op_strings(Sass::Operand, Value&, Value&, struct Sass_Inspect_Options opt, const SourceSpan& pstate, bool delayed = false); - Value* op_colors(enum Sass_OP, const Color_RGBA&, const Color_RGBA&, struct Sass_Inspect_Options opt, const SourceSpan& pstate, bool delayed = false); - Value* op_numbers(enum Sass_OP, const Number&, const Number&, struct Sass_Inspect_Options opt, const SourceSpan& pstate, bool delayed = false); - Value* op_number_color(enum Sass_OP, const Number&, const Color_RGBA&, struct Sass_Inspect_Options opt, const SourceSpan& pstate, bool delayed = false); - Value* op_color_number(enum Sass_OP, const Color_RGBA&, const Number&, struct Sass_Inspect_Options opt, const SourceSpan& pstate, bool delayed = false); - - }; - -} - -#endif diff --git a/src/ordered_map.hpp b/src/ordered_map.hpp index 6b70f80536..c8e7f6120f 100644 --- a/src/ordered_map.hpp +++ b/src/ordered_map.hpp @@ -1,111 +1,336 @@ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ #ifndef SASS_ORDERED_MAP_H #define SASS_ORDERED_MAP_H +#include +#include +#include "memory.hpp" + +// #include "../../hopscotch-map/include/tsl/hopscotch_map.h" +// #include "../../hopscotch-map/include/tsl/hopscotch_set.h" + namespace Sass { - // ########################################################################## - // Very simple and limited container for insert ordered hash map. - // Piggy-back implementation on std::unordered_map and sass::vector - // ########################################################################## + ///////////////////////////////////////////////////////////////////////// + // Very simple and limited container for insertion ordered hash map. + // Piggy-back implementation on std::unordered_map and std::vector. + ///////////////////////////////////////////////////////////////////////// + // In order to support assignable value references, we can only store the + // value in one container. We can't reference values from one container in + // another, since the pointer would be invalidated, once a container needs + // re-allocation. To fix this we need a soft reference. Therefore we only + // store the index into the list vector on the hash-map. This makes all + // access operations constant, but makes erasing of items, or insertion + // not at the end (which is not yet supported) in the worst case O(n), as + // we need to adjust the indexes on the hash-map after the modified item. + ///////////////////////////////////////////////////////////////////////// template< - class Key, - class T, - class Hash = std::hash, - class KeyEqual = std::equal_to, - class Allocator = std::allocator> + class TKey, + class TVal, + class Hash = std::hash, + class KeyEqual = std::equal_to, + class ListAllocator = Sass::Allocator>, + class MapAllocator = Sass::Allocator> > class ordered_map { - private: - - using map_type = typename std::unordered_map< Key, T, Hash, KeyEqual, Allocator>; - using map_iterator = typename std::unordered_map< Key, T, Hash, KeyEqual, Allocator>::iterator; - using map_const_iterator = typename std::unordered_map< Key, T, Hash, KeyEqual, Allocator>::const_iterator; - - // The main unordered map - map_type _map; - - // Keep insertion order - sass::vector _keys; - sass::vector _values; - - const KeyEqual _keyEqual; - - public: - - ordered_map() : - _keyEqual(KeyEqual()) - { - } - - ordered_map& operator= (const ordered_map& other) { - _map = other._map; - _keys = other._keys; - _values = other._values; - return *this; - } - - std::pair front() { - return std::make_pair( - _keys.front(), - _values.front() - ); - } - - bool empty() const { - return _keys.empty(); - } - - void insert(const Key& key, const T& val) { - if (!hasKey(key)) { - _values.push_back(val); - _keys.push_back(key); - } - _map[key] = val; - } - - bool hasKey(const Key& key) const { - return _map.find(key) != _map.end(); - } - - bool erase(const Key& key) { - _map.erase(key); - // find the position in the array - for (size_t i = 0; i < _keys.size(); i += 1) { - if (_keyEqual(key, _keys[i])) { - _keys.erase(_keys.begin() + i); - _values.erase(_values.begin() + i); - return true; + using map_type = typename std::unordered_map < + TKey, size_t, Hash, KeyEqual, MapAllocator >; + using pair_type = typename std::pair; + using list_type = typename std::vector; + + private: + + // The main unordered map for key access + map_type _map; + + // The insertion ordered list of kv-pairs + list_type _list; + + // Instance of equality functor + const KeyEqual _keyEqual; + + public: + + // Default constructor + ordered_map() : + _map(map_type()), + _list(list_type()), + _keyEqual(KeyEqual()) + {} + + // Explicit destructor for rule of three + // ~ordered_map() {} + + // The copy constructor for rule of three + ordered_map(const ordered_map* ptr) : + _map(ptr->_map), + _list(ptr->_list), + _keyEqual(ptr->_keyEqual) + {} + + // The copy constructor for rule of three + ordered_map(const ordered_map& other) : + _map(other._map), + _list(other._list), + _keyEqual(other._keyEqual) + {} + + // The move constructor for rule of three + ordered_map(ordered_map&& other) : + _map(std::move(other._map)), + _list(std::move(other._list)), + _keyEqual(other._keyEqual) + {} + + // The copy assignment operator for rule of three + ordered_map& operator= (const ordered_map& other) + { + _map = other._map; + _list = other._list; + // _keyEqual = other._keyEqual; + return *this; + } + + // The move assignment operator for rule of three + ordered_map& operator= (ordered_map&& other) + { + _map = std::move(other._map); + _list = std::move(other._list); + // _keyEqual = other._keyEqual; + return *this; + } + + ///////////////////////////////////////////////////////////////////////// + // Implement vector API partially (return pairs) + ///////////////////////////////////////////////////////////////////////// + + // Returns number of pairs (or keys). + // Normal maps report double the size. + size_t size() const + { + return _list.size(); + } + // EO size + + // Returns true if map is empty + bool empty() const + { + return _list.empty(); + } + // EO empty + + // Get kv_pair for last item. + // Throws out of boundary exception. + pair_type& front() + { + return _list.front(); + } + // EO front + + // Get kv_pair for last item. + // Throws out of boundary exception. + pair_type& back() + { + return _list.back(); + } + // EO back + + ///////////////////////////////////////////////////////////////////////// + // Implement unordered_map API partially + ///////////////////////////////////////////////////////////////////////// + + // Returns 1 if the key exists + size_t count(const TKey& key) const { + return _map.count(key); + } + // EO count(key) + + // Find key and return ordered list iterator + typename list_type::const_iterator find(const TKey& key) const { + typename map_type::const_iterator existing = _map.find(key); + if (existing == _map.end()) return _list.end(); + return _list.begin() + existing->second; + } + // EO find(key) const + + // Find key and return ordered list iterator + typename list_type::iterator find(const TKey& key) { + typename map_type::iterator existing = _map.find(key); + if (existing == _map.end()) return _list.end(); + return _list.begin() + existing->second; + } + // EO find(key) + + ///////////////////////////////////////////////////////////////////////// + // Implement mixed manipulation API + ///////////////////////////////////////////////////////////////////////// + + // Append a new key/value pair + void push_back(const pair_type& kv, bool overwrite = false) + { + typename map_type::iterator existing = _map.find(kv.first); + // Check if key is already existing + if (existing != _map.end()) { + if (overwrite) { + size_t idx = existing->second; + _list[idx].second = kv.second; + } + else { + throw std::logic_error("Key already exists"); + } + } + // Append new key and value + else { + _map[kv.first] = size(); + _list.emplace_back(kv); + } + } + // EO push_back(kv_pair) + + // Append a new key/value pair + void push_back(const TKey& key, const TVal& value, bool overwrite = false) + { + push_back(std::make_pair(key, value), overwrite); + } + // EO push_back(key, value) + + // Overwrite existing item or append it + void set(const TKey& key, const TVal& value) + { + push_back(key, value, true); + } + // EO set(key, value) + + // Remove item by key + bool erase(const TKey& key) { + // Remove from map + _map.erase(key); + // Find the position in the array + for (size_t i = 0; i < size(); i += 1) { + if (_keyEqual(key, _list[i].first)) { + _list.erase(_list.begin() + i); + // Adjust all indexes after the removal + for (size_t j = i; j < size(); j += 1) { + --_map[_list[j].first]; // reduce by one + } + return true; + } + } + return false; + } + // EO erase(key) + + // Remove item by index + bool erase(size_t idx) { + // Gracefully handle out of bound + if (idx < 0 || idx >= size()) return false; + // Remove key to index + _map.erase(_list[idx].first); + // Remove key / value pair + _list.erase(_list.begin() + idx); + // Adjust all indexes after the removal + for (size_t j = idx; j < size(); j += 1) { + --_map[_list[j].first]; // reduce by one + } + return true; + } + // EO erase(index) + + // Get item from map, if missing it will + // be created and default initialized. + TVal& operator[](const TKey& key) { + typename map_type::iterator existing = _map.find(key); + // Check if key is already existing + if (existing != _map.end()) { + return _list[existing->second].second; } + _map[key] = _list.size(); + // We must default initialize the value! + _list.emplace_back(std::make_pair(key, TVal())); + return _list.back().second; + } + // EO operator[](key) + + // Get item from list by index. + pair_type& operator[](size_t idx) { + return _list[idx]; } - return false; - } + // EO operator[](index) - const sass::vector& keys() const { return _keys; } - const sass::vector& values() const { return _values; } + // Get item from list by index. + // Throws out of boundary error. + pair_type& at(size_t idx) { + return _list.at(idx); + } + // EO at(index) + + ///////////////////////////////////////////////////////////////////////// + // Some additional stuff + ///////////////////////////////////////////////////////////////////////// - const T& get(const Key& key) { - if (hasKey(key)) { - return _map[key]; + // Reserve memory + void reserve(size_t size) + { + _map.reserve(size); + _list.reserve(size); } - throw std::runtime_error("Key does not exist"); - } + // EO reserve + + ///////////////////////////////////////////////////////////////////////// + // Some syntax sugar API + ///////////////////////////////////////////////////////////////////////// + + // Note that this creates a new array every time. + // Only call it if you really want to have a copy. + sass::vector keys() const { + sass::vector keys; + keys.reserve(size()); + for (const pair_type& kv : _list) { + keys.emplace_back(kv.first); + } + return keys; + } + // EO keys + + // Note that this creates a new array every time. + // Only call it if you really want to have a copy. + sass::vector values() const { + sass::vector values; + values.reserve(size()); + for (const pair_type& kv : _list) { + values.emplace_back(kv.second); + } + return values; + } + // EO values + + ///////////////////////////////////////////////////////////////////////// + // Create iterator aliases for ourself + ///////////////////////////////////////////////////////////////////////// + + using iterator = typename list_type::iterator; + using const_iterator = typename list_type::const_iterator; + using reverse_iterator = typename list_type::reverse_iterator; + using const_reverse_iterator = typename list_type::const_reverse_iterator; - using iterator = typename sass::vector::iterator; - using const_iterator = typename sass::vector::const_iterator; - using reverse_iterator = typename sass::vector::reverse_iterator; - using const_reverse_iterator = typename sass::vector::const_reverse_iterator; + ///////////////////////////////////////////////////////////////////////// + // Implement iterator functions + ///////////////////////////////////////////////////////////////////////// - typename sass::vector::iterator end() { return _keys.end(); } - typename sass::vector::iterator begin() { return _keys.begin(); } - typename sass::vector::reverse_iterator rend() { return _keys.rend(); } - typename sass::vector::reverse_iterator rbegin() { return _keys.rbegin(); } - typename sass::vector::const_iterator end() const { return _keys.end(); } - typename sass::vector::const_iterator begin() const { return _keys.begin(); } - typename sass::vector::const_reverse_iterator rend() const { return _keys.rend(); } - typename sass::vector::const_reverse_iterator rbegin() const { return _keys.rbegin(); } + iterator end() { return _list.end(); } + iterator begin() { return _list.begin(); } + reverse_iterator rend() { return _list.rend(); } + reverse_iterator rbegin() { return _list.rbegin(); } + const_iterator end() const { return _list.end(); } + const_iterator begin() const { return _list.begin(); } + const_reverse_iterator rend() const { return _list.rend(); } + const_reverse_iterator rbegin() const { return _list.rbegin(); } }; + // EO ordered_map } diff --git a/src/output.cpp b/src/output.cpp index 0748cd4643..5aa91f2a08 100644 --- a/src/output.cpp +++ b/src/output.cpp @@ -1,320 +1,251 @@ -// sass.hpp must go before all system headers to get the -// __EXTENSIONS__ fix on Solaris. -#include "sass.hpp" - -#include "ast.hpp" +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ #include "output.hpp" -#include "util.hpp" + +#include "ast_css.hpp" +#include "ast_values.hpp" +#include "charcode.hpp" +#include "character.hpp" +#include "exceptions.hpp" namespace Sass { - Output::Output(Sass_Output_Options& opt) - : Inspect(Emitter(opt)), - charset(""), - top_nodes(0) - {} + // Import some namespaces + using namespace Charcode; + using namespace Character; - Output::~Output() { } + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// - void Output::fallback_impl(AST_Node* n) - { - return n->perform(this); - } - - void Output::operator()(Number* n) - { - // check for a valid unit here - // includes result for reporting - if (!n->is_valid_css_unit()) { - // should be handle in check_expression - throw Exception::InvalidValue({}, *n); - } - // use values to_string facility - sass::string res = n->to_string(opt); - // output the final token - append_token(res, n); - } + // Value constructor + Output::Output( + OutputOptions& opt) : + Cssize(opt) + {} - void Output::operator()(Import* imp) - { - top_nodes.push_back(imp); - } + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// - void Output::operator()(Map* m) + // Local helper function to right-trim multi-line text. + // Only the last line is right trimmed in an optimized way. + // Note: this is merely cosmetic to match dart-sass output. + void trim_trailing_lines(sass::string& text) { - // should be handle in check_expression - throw Exception::InvalidValue({}, *m); + auto start = text.begin(); + auto lastlf = text.end(); + auto end = lastlf - 1; + while (end != start) { + if (Character::isNewline(*end)) { + lastlf = end; + end--; + } + else if (Character::isWhitespace(*end)) { + end--; + } + else { + break; + } + } + if (lastlf != text.end()) { + text = sass::string(start, lastlf) + " "; + } } - - OutputBuffer Output::get_buffer(void) + // EO string_trim_trailing_lines + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // Get the output buffer for what has been rendered. + // This will actually invoke quite a bit under the hood. + // First we will render the hoisted imports with comments. + // Then we will prepend this string to the already rendered one. + // This involves adjusting the source-map so mappings still match. + // Note: this is less complicated than one could initially think, as + // Note: source-maps are easy to manipulate with full lines only. + OutputBuffer Output::getBuffer(void) { + // Create an identical rendered for the hoisted part + Inspect inspect(outopt); - Emitter emitter(opt); - Inspect inspect(emitter); - - size_t size_nodes = top_nodes.size(); - for (size_t i = 0; i < size_nodes; i++) { - top_nodes[i]->perform(&inspect); + // Render the hoisted imports with comments + for (size_t i = 0; i < imports.size(); i++) { + imports[i]->accept(&inspect); inspect.append_mandatory_linefeed(); } - // flush scheduled outputs // maybe omit semicolon if possible inspect.finalize(wbuf.buffer.size() == 0); // prepend buffer on top prepend_output(inspect.output()); + // flush trailing comments + flushCssComments(); + // make sure we end with a linefeed - if (!ends_with(wbuf.buffer, opt.linefeed)) { + if (!StringUtils::endsWith(wbuf.buffer, outopt.linefeed)) { // if the output is not completely empty - if (!wbuf.buffer.empty()) append_string(opt.linefeed); + if (!wbuf.buffer.empty()) append_string(outopt.linefeed); } // search for unicode char - for(const char& chr : wbuf.buffer) { - // skip all ascii chars - // static cast to unsigned to handle `char` being signed / unsigned + for (const char& chr : wbuf.buffer) { + // skip all ASCII chars if (static_cast(chr) < 128) continue; // declare the charset - if (output_style() != COMPRESSED) + if (output_style() != SASS_STYLE_COMPRESSED) charset = "@charset \"UTF-8\";" - + sass::string(opt.linefeed); + + sass::string(outopt.linefeed); else charset = "\xEF\xBB\xBF"; // abort search break; } // add charset as first line, before comments and imports + // adding any unicode BOM should not alter source-maps if (!charset.empty()) prepend_string(charset); - return wbuf; - + // pass the buffer back + return std::move(wbuf); } + // EO getBuffer - void Output::operator()(Comment* c) - { - // if (indentation && txt == "/**/") return; - bool important = c->is_important(); - if (output_style() != COMPRESSED || important) { - if (buffer().size() == 0) { - top_nodes.push_back(c); - } else { - in_comment = true; - append_indentation(); - c->text()->perform(this); - in_comment = false; - if (indentation == 0) { - append_mandatory_linefeed(); - } else { - append_optional_linefeed(); - } - } - } - } + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// - void Output::operator()(StyleRule* r) + // Helper function to append comment to output + void Output::printCssComment(CssComment* comment) { - Block_Obj b = r->block(); - SelectorListObj s = r->selector(); - - if (!s || s->empty()) return; - - // Filter out rulesets that aren't printable (process its children though) - if (!Util::isPrintable(r, output_style())) { - for (size_t i = 0, L = b->length(); i < L; ++i) { - const Statement_Obj& stm = b->get(i); - if (Cast(stm)) { - if (!Cast(stm)) { - stm->perform(this); - } - } - } - return; - } - - if (output_style() == NESTED) { - indentation += r->tabs(); - } - if (opt.source_comments) { - sass::ostream ss; + // Ignore sourceMappingURL and sourceURL comments (ToDo: use regexp?). + if (StringUtils::startsWith(comment->text(), "/*# sourceMappingURL=")) return; + else if (StringUtils::startsWith(comment->text(), "/*# sourceURL=")) return; + bool important = comment->isPreserved(); + if (output_style() == SASS_STYLE_COMPRESSED || output_style() == SASS_STYLE_COMPACT) { + if (!important) return; + } + if (output_style() != SASS_STYLE_COMPRESSED || important) { append_indentation(); - sass::string path(File::abs2rel(r->pstate().getPath())); - ss << "/* line " << r->pstate().getLine() << ", " << path << " */"; - append_string(ss.str()); - append_optional_linefeed(); - } - scheduled_crutch = s; - if (s) s->perform(this); - append_scope_opener(b); - for (size_t i = 0, L = b->length(); i < L; ++i) { - Statement_Obj stm = b->get(i); - bool bPrintExpression = true; - // Check print conditions - if (Declaration* dec = Cast(stm)) { - if (const String_Constant* valConst = Cast(dec->value())) { - const sass::string& val = valConst->value(); - if (const String_Quoted* qstr = Cast(valConst)) { - if (!qstr->quote_mark() && val.empty()) { - bPrintExpression = false; - } - } - } - else if (List* list = Cast(dec->value())) { - bool all_invisible = true; - for (size_t list_i = 0, list_L = list->length(); list_i < list_L; ++list_i) { - Expression* item = list->get(list_i); - if (!item->is_invisible()) all_invisible = false; - } - if (all_invisible && !list->is_bracketed()) bPrintExpression = false; - } + append_string(comment->text()); + if (indentation == 0) { + append_mandatory_linefeed(); } - // Print if OK - if (bPrintExpression) { - stm->perform(this); + else { + append_optional_linefeed(); } } - if (output_style() == NESTED) indentation -= r->tabs(); - append_scope_closer(b); - } - void Output::operator()(Keyframe_Rule* r) - { - Block_Obj b = r->block(); - Selector_Obj v = r->name(); + // EO printCssComment - if (!v.isNull()) { - v->perform(this); + // Flushes all queued comments to output + // Also resets and clears the queue + void Output::flushCssComments() + { + for (CssComment* comment : comments) { + printCssComment(comment); } + comments.clear(); + } + // EO flushCssComments - if (!b) { - append_colon_separator(); - return; - } + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// - append_scope_opener(); - for (size_t i = 0, L = b->length(); i < L; ++i) { - Statement_Obj stm = b->get(i); - stm->perform(this); - if (i < L - 1) append_special_linefeed(); + void Output::visitCssImport(CssImport* imp) + { + if (imp->outOfOrder()) { + imports.insert(imports.end(), + comments.begin(), comments.end()); + imports.emplace_back(imp); + comments.clear(); + } + else { + // This case is possible if an `@import` within + // an imported css file is inside a `CssStyleRule`. + flushCssComments(); + Cssize::visitCssImport(imp); } - append_scope_closer(); } + // EO visitCssImport - void Output::operator()(SupportsRule* f) + void Output::visitCssComment(CssComment* comment) { - if (f->is_invisible()) return; - - SupportsConditionObj c = f->condition(); - Block_Obj b = f->block(); - - // Filter out feature blocks that aren't printable (process its children though) - if (!Util::isPrintable(f, output_style())) { - for (size_t i = 0, L = b->length(); i < L; ++i) { - Statement_Obj stm = b->get(i); - if (Cast(stm)) { - stm->perform(this); - } - } - return; + if (hoistComments) { + comments.emplace_back(comment); } - - if (output_style() == NESTED) indentation += f->tabs(); - append_indentation(); - append_token("@supports", f); - append_mandatory_space(); - c->perform(this); - append_scope_opener(); - - for (size_t i = 0, L = b->length(); i < L; ++i) { - Statement_Obj stm = b->get(i); - stm->perform(this); - if (i < L - 1) append_special_linefeed(); + else { + printCssComment(comment); } + } + // EO visitCssComment - if (output_style() == NESTED) indentation -= f->tabs(); + void Output::visitCssStyleRule(CssStyleRule* rule) + { + // Prepend previous comments + flushCssComments(); + // Never hoist nested comments + RAII_FLAG(hoistComments, false); + // Let inspect do its magic + Cssize::visitCssStyleRule(rule); + } + // EO visitCssStyleRule - append_scope_closer(); + void Output::visitCssSupportsRule(CssSupportsRule* rule) + { + // Prepend previous comments + flushCssComments(); + // Never hoist nested comments + RAII_FLAG(hoistComments, false); + // Let inspect do its magic + Cssize::visitCssSupportsRule(rule); + } + // EO visitCssSupportsRule + void Output::visitCssAtRule(CssAtRule* rule) + { + // Prepend previous comments + flushCssComments(); + // Never hoist nested comments + RAII_FLAG(hoistComments, false); + // Let inspect do its magic + Cssize::visitCssAtRule(rule); } + // EO visitCssAtRule - void Output::operator()(CssMediaRule* rule) + void Output::visitCssMediaRule(CssMediaRule* rule) { // Avoid null pointer exception if (rule == nullptr) return; - // Skip empty/invisible rule - if (rule->isInvisible()) return; - // Avoid null pointer exception - if (rule->block() == nullptr) return; - // Skip empty/invisible rule - if (rule->block()->isInvisible()) return; - // Skip if block is empty/invisible - if (Util::isPrintable(rule, output_style())) { - // Let inspect do its magic - Inspect::operator()(rule); - } + // Skip empty or invisible rule + if (rule->isInvisibleCss()) return; + // Prepend previous comments + flushCssComments(); + // Never hoist nested comments + RAII_FLAG(hoistComments, false); + // Let inspect do its magic + Cssize::visitCssMediaRule(rule); } + // EO visitCssMediaRule - void Output::operator()(AtRule* a) + void Output::visitMap(Map* m) { - sass::string kwd = a->keyword(); - Selector_Obj s = a->selector(); - ExpressionObj v = a->value(); - Block_Obj b = a->block(); - - append_indentation(); - append_token(kwd, a); - if (s) { - append_mandatory_space(); - in_wrapped = true; - s->perform(this); - in_wrapped = false; - } - if (v) { - append_mandatory_space(); - // ruby sass bug? should use options? - append_token(v->to_string(/* opt */), v); - } - if (!b) { - append_delimiter(); - return; - } - - if (b->is_invisible() || b->length() == 0) { - append_optional_space(); - return append_string("{}"); - } - - append_scope_opener(); - - bool format = kwd != "@font-face";; - - for (size_t i = 0, L = b->length(); i < L; ++i) { - Statement_Obj stm = b->get(i); - if (stm) stm->perform(this); - if (i < L - 1 && format) append_special_linefeed(); - } - - append_scope_closer(); + // should be handle in check_expression + throw Exception::InvalidCssValue({}, *m); } + // EO visitMap - void Output::operator()(String_Quoted* s) + void Output::visitString(String* s) { - if (s->quote_mark()) { - append_token(quote(s->value(), s->quote_mark()), s); - } else if (!in_comment) { - append_token(string_to_output(s->value()), s); - } else { - append_token(s->value(), s); + if (!in_custom_property) { + Inspect::visitString(s); } - } - - void Output::operator()(String_Constant* s) - { - sass::string value(s->value()); - if (!in_comment && !in_custom_property) { - append_token(string_to_output(value), s); - } else { + else { + sass::string value(s->value()); + trim_trailing_lines(value); append_token(value, s); } } + // EO visitString + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// } diff --git a/src/output.hpp b/src/output.hpp index b05ff3c807..c4f3bc94d3 100644 --- a/src/output.hpp +++ b/src/output.hpp @@ -1,44 +1,80 @@ -#ifndef SASS_OUTPUT_H -#define SASS_OUTPUT_H +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#ifndef SASS_OUTPUT_HPP +#define SASS_OUTPUT_HPP -#include -#include +// sass.hpp must go before all system headers +// to get the __EXTENSIONS__ fix on Solaris. +#include "capi_sass.hpp" -#include "util.hpp" -#include "inspect.hpp" -#include "operation.hpp" +#include "cssize.hpp" namespace Sass { - class Context; - class Output : public Inspect { + // Output is responsible to hoist imports and comments correctly. + // We want comments occurring before an import to be hoisted with it. + // Also applies to rules hoisted down due to imports being hoisted up. + // We want to preserve comments occurring before important blocks. + class Output : public Cssize + { protected: - using Inspect::operator(); - public: - Output(Sass_Output_Options& opt); - virtual ~Output(); - - protected: + // The parsed charset sass::string charset; - sass::vector top_nodes; + + // Hoist imports and their comments in the whole document. + // This shuffles imports in the middle of a file to the top. + CssNodeVector imports; + + // Flag if comments should be queued for hoisting. + // This is typically only allowed on the root level. + bool hoistComments = true; + + // The comments we queued up so far + // Flush/process once we know what to do + sass::vector comments; + + // Helper function to append comment to output + void printCssComment(CssComment* comment); + + // Flushes all queued comments to output + // Also resets and clears the queue + void flushCssComments(); public: - OutputBuffer get_buffer(void); - - virtual void operator()(Map*); - virtual void operator()(StyleRule*); - virtual void operator()(SupportsRule*); - virtual void operator()(CssMediaRule*); - virtual void operator()(AtRule*); - virtual void operator()(Keyframe_Rule*); - virtual void operator()(Import*); - virtual void operator()(Comment*); - virtual void operator()(Number*); - virtual void operator()(String_Quoted*); - virtual void operator()(String_Constant*); - - void fallback_impl(AST_Node* n); + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // Value constructor + Output(OutputOptions& opt); + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // Return buffer to compiler + OutputBuffer getBuffer(void); + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + virtual void visitCssImport(CssImport*) override; + virtual void visitCssComment(CssComment*) override; + virtual void visitCssMediaRule(CssMediaRule*) override; + + virtual void visitCssAtRule(CssAtRule*) override; + virtual void visitCssStyleRule(CssStyleRule*) override; + virtual void visitCssSupportsRule(CssSupportsRule*) override; + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + virtual void visitMap(Map* value) override; + virtual void visitString(String* value) override; + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// }; diff --git a/src/parser.cpp b/src/parser.cpp index 201a536ef6..b99185800c 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -1,2965 +1,692 @@ -// sass.hpp must go before all system headers to get the -// __EXTENSIONS__ fix on Solaris. -#include "sass.hpp" - +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ #include "parser.hpp" -#include "color_maps.hpp" -#include "util_string.hpp" - -// Notes about delayed: some ast nodes can have delayed evaluation so -// they can preserve their original semantics if needed. This is most -// prominently exhibited by the division operation, since it is not -// only a valid operation, but also a valid css statement (i.e. for -// fonts, as in `16px/24px`). When parsing lists and expression we -// unwrap single items from lists and other operations. A nested list -// must not be delayed, only the items of the first level sometimes -// are delayed (as with argument lists). To achieve this we need to -// pass status to the list parser, so this can be set correctly. -// Another case with delayed values are colors. In compressed mode -// only processed values get compressed (other are left as written). +#include "sources.hpp" +#include "compiler.hpp" +#include "charcode.hpp" +#include "character.hpp" +#include "exceptions.hpp" +#include "ast_statements.hpp" namespace Sass { - using namespace Constants; - using namespace Prelexer; - - - Parser::Parser(SourceData* source, Context& ctx, Backtraces traces, bool allow_parent) : - SourceSpan(source), - ctx(ctx), - source(source), - begin(source->begin()), - position(source->begin()), - end(source->end()), - before_token(0, 0), - after_token(0, 0), - pstate(source->getSourceSpan()), - traces(traces), - indentation(0), - nestings(0), - allow_parent(allow_parent) - { - Block_Obj root = SASS_MEMORY_NEW(Block, pstate); - stack.push_back(Scope::Root); - block_stack.push_back(root); - root->is_root(true); - } - void Parser::advanceToNextToken() { - lex < css_comments >(false); - // advance to position - pstate.position += pstate.offset; - pstate.offset.column = 0; - pstate.offset.line = 0; + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // Import some namespaces + using namespace Charcode; + using namespace Character; + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // Constructor + Parser::Parser( + Compiler& compiler, + SourceDataObj source) : + compiler(compiler), + modctx(compiler.modctx3), + wconfig(compiler.wconfig), + // hasWithConfig(compiler.hasWithConfig), + scanner(compiler, source), + varStack(compiler.varRoot.stack), + lastSilentComment() + {} + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // ToDo: implement without the try clause + // ToDo: measure if this brings any speed? + bool Parser::isIdentifier(sass::string text) + { + try { + auto src = SASS_MEMORY_NEW(SourceString, + "sass:://identifier", std::move(text)); + Parser parser(compiler, src); + sass::string id(parser.readIdentifier()); + return parser.scanner.isDone(); + } + catch (Exception::ParserException&) { + return false; + } + } + + // Consumes and ignores a comment if possible. + // Returns whether the comment was consumed. + bool Parser::scanComment() + { + if (scanner.peekChar() != $slash) return false; + auto next = scanner.peekChar(1); + if (next == $slash) { + //lastSilentComment = read + scanSilentComment(); + return true; + } + else if (next == $asterisk) { + scanLoudComment(); + return true; + } + else { + return false; } - - SelectorListObj Parser::parse_selector(SourceData* source, Context& ctx, Backtraces traces, bool allow_parent) - { - Parser p(source, ctx, traces, allow_parent); - // ToDo: remap the source-map entries somehow - return p.parseSelectorList(false); } - bool Parser::peek_newline(const char* start) + // Consumes and ignores a silent (Sass-style) comment. + void Parser::scanSilentComment() { - return peek_linefeed(start ? start : position) - && ! peek_css>(start); + scanner.expect("//"); + while (!scanner.isDone() && !isNewline(scanner.peekChar())) { + scanner.readChar(); + } } - /* main entry point to parse root block */ - Block_Obj Parser::parse() + // Consumes and ignores a loud (CSS-style) comment. + void Parser::scanLoudComment() { + scanner.expect("/*"); + while (true) { + auto next = scanner.readChar(); + if (next != $asterisk) continue; - // consume unicode BOM - read_bom(); - - // scan the input to find invalid utf8 sequences - const char* it = utf8::find_invalid(position, end); - - // report invalid utf8 - if (it != end) { - pstate.position += Offset::init(position, it); - traces.push_back(Backtrace(pstate)); - throw Exception::InvalidSass(pstate, traces, "Invalid UTF-8 sequence"); - } - - // create a block AST node to hold children - Block_Obj root = SASS_MEMORY_NEW(Block, pstate, 0, true); - - // check seems a bit esoteric but works - if (ctx.resources.size() == 1) { - // apply headers only on very first include - ctx.apply_custom_headers(root, getPath(), pstate); - } - - // parse children nodes - block_stack.push_back(root); - parse_block_nodes(true); - block_stack.pop_back(); - - // update final position - root->update_pstate(pstate); - - if (position != end) { - css_error("Invalid CSS", " after ", ": expected selector or at-rule, was "); + do { + next = scanner.readChar(); + } while (next == $asterisk); + if (next == $slash) break; } - - return root; } - - // convenience function for block parsing - // will create a new block ad-hoc for you - // this is the base block parsing function - Block_Obj Parser::parse_css_block(bool is_root) + // Consumes a plain CSS identifier. If [unit] is `true`, this + // doesn't parse a `-` followed by a digit. This ensures that + // `1px-2px` parses as subtraction rather than the unit `px-2px`. + sass::string Parser::readIdentifier(bool unit) { - // parse comments before block - // lex < optional_css_comments >(); + // NOTE: this logic is largely duplicated in StylesheetParser._interpolatedIdentifier + // and isIdentifier in utils.dart. Most changes here should be mirrored there. - // lex mandatory opener or error out - if (!lex_css < exactly<'{'> >()) { - css_error("Invalid CSS", " after ", ": expected \"{\", was "); + Offset start(scanner.offset); + StringBuffer text; + if (scanner.scanChar($minus)) { + text.write($minus); + if (scanner.scanChar($minus)) { + text.writeCharCode($minus); + consumeIdentifierBody(text, unit); + return text.buffer; + } } - // create new block and push to the selector stack - Block_Obj block = SASS_MEMORY_NEW(Block, pstate, 0, is_root); - block_stack.push_back(block); - if (!parse_block_nodes(is_root)) css_error("Invalid CSS", " after ", ": expected \"}\", was "); - - if (!lex_css < exactly<'}'> >()) { - css_error("Invalid CSS", " after ", ": expected \"}\", was "); + auto first = scanner.peekChar(); + if (first == $nul) { + error("Expected identifier.", + scanner.rawSpanFrom(start)); + } + else if (isNameStart(first)) { + text.write(scanner.readChar()); + } + else if (first == $backslash) { + escape(text, /*identifierStart*/true); + } + else { + error("Expected identifier.", + scanner.rawSpanFrom(start)); } - // update for end position - // this seems to be done somewhere else - // but that fixed selector schema issue - // block->update_pstate(pstate); - - // parse comments after block - // lex < optional_css_comments >(); - - block_stack.pop_back(); + consumeIdentifierBody(text, unit); - return block; - } + return std::move(text.buffer); - // convenience function for block parsing - // will create a new block ad-hoc for you - // also updates the `in_at_root` flag - Block_Obj Parser::parse_block(bool is_root) - { - return parse_css_block(is_root); } - // the main block parsing function - // parses stuff between `{` and `}` - bool Parser::parse_block_nodes(bool is_root) + // Consumes a chunk of a plain CSS identifier after the name start. + sass::string Parser::identifierBody() { - - // loop until end of string - while (position < end) { - - // we should be able to refactor this - parse_block_comments(); - lex < css_whitespace >(); - - if (lex < exactly<';'> >()) continue; - if (peek < end_of_file >()) return true; - if (peek < exactly<'}'> >()) return true; - - if (parse_block_node(is_root)) continue; - - parse_block_comments(); - - if (lex_css < exactly<';'> >()) continue; - if (peek_css < end_of_file >()) return true; - if (peek_css < exactly<'}'> >()) return true; - - // illegal sass - return false; + StringBuffer text; + consumeIdentifierBody(text); + if (text.empty()) { + error( + "Expected identifier body.", + scanner.rawSpan()); } - // return success - return true; + return text.buffer; } - // parser for a single node in a block - // semicolons must be lexed beforehand - bool Parser::parse_block_node(bool is_root) { - - Block_Obj block = block_stack.back(); - - parse_block_comments(); - - // throw away white-space - // includes line comments - lex < css_whitespace >(); - - Lookahead lookahead_result; - - // also parse block comments - - // first parse everything that is allowed in functions - if (lex < variable >(true)) { block->append(parse_assignment()); } - else if (lex < kwd_err >(true)) { block->append(parse_error()); } - else if (lex < kwd_dbg >(true)) { block->append(parse_debug()); } - else if (lex < kwd_warn >(true)) { block->append(parse_warning()); } - else if (lex < kwd_if_directive >(true)) { block->append(parse_if_directive()); } - else if (lex < kwd_for_directive >(true)) { block->append(parse_for_directive()); } - else if (lex < kwd_each_directive >(true)) { block->append(parse_each_directive()); } - else if (lex < kwd_while_directive >(true)) { block->append(parse_while_directive()); } - else if (lex < kwd_return_directive >(true)) { block->append(parse_return_directive()); } - - // parse imports to process later - else if (lex < kwd_import >(true)) { - Scope parent = stack.empty() ? Scope::Rules : stack.back(); - if (parent != Scope::Function && parent != Scope::Root && parent != Scope::Rules && parent != Scope::Media) { - if (! peek_css< uri_prefix >(position)) { // this seems to go in ruby sass 3.4.20 - error("Import directives may not be used within control directives or mixins."); - } + // Like [consumeIdentifierBody], but parses the body into the [text] buffer. + void Parser::consumeIdentifierBody(StringBuffer& text, bool unit) + { + while (true) { + uint8_t next = scanner.peekChar(); + if (next == $nul) { + break; } - // this puts the parsed doc into sheets - // import stub will fetch this in expand - Import_Obj imp = parse_import(); - // if it is a url, we only add the statement - if (!imp->urls().empty()) block->append(imp); - // process all resources now (add Import_Stub nodes) - for (size_t i = 0, S = imp->incs().size(); i < S; ++i) { - block->append(SASS_MEMORY_NEW(Import_Stub, pstate, imp->incs()[i])); + else if (unit && next == $minus) { + // Disallow `-` followed by a dot or a digit in units. + uint8_t second = scanner.peekChar(1); + if (second != $nul && (second == $dot || isDigit(second))) break; + text.write(scanner.readChar()); } - } - - else if (lex < kwd_extend >(true)) { - Lookahead lookahead = lookahead_for_include(position); - if (!lookahead.found) css_error("Invalid CSS", " after ", ": expected selector, was "); - SelectorListObj target; - if (!lookahead.has_interpolants) { - LOCAL_FLAG(allow_parent, false); - auto selector = parseSelectorList(true); - auto extender = SASS_MEMORY_NEW(ExtendRule, pstate, selector); - extender->isOptional(selector && selector->is_optional()); - block->append(extender); + else if (isName(next)) { + text.write(scanner.readChar()); + } + else if (next == $backslash) { + escape(text); } else { - LOCAL_FLAG(allow_parent, false); - auto selector = parse_selector_schema(lookahead.found, true); - auto extender = SASS_MEMORY_NEW(ExtendRule, pstate, selector); - // A schema is not optional yet, check once it is evaluated - // extender->isOptional(selector && selector->is_optional()); - block->append(extender); + break; } - } + } - // selector may contain interpolations which need delayed evaluation - else if ( - !(lookahead_result = lookahead_for_selector(position)).error && - !lookahead_result.is_custom_property - ) - { - block->append(parse_ruleset(lookahead_result)); - } - - // parse multiple specific keyword directives - else if (lex < kwd_media >(true)) { block->append(parseMediaRule()); } - else if (lex < kwd_at_root >(true)) { block->append(parse_at_root_block()); } - else if (lex < kwd_include_directive >(true)) { block->append(parse_include_directive()); } - else if (lex < kwd_content_directive >(true)) { block->append(parse_content_directive()); } - else if (lex < kwd_supports_directive >(true)) { block->append(parse_supports_directive()); } - else if (lex < kwd_mixin >(true)) { block->append(parse_definition(Definition::MIXIN)); } - else if (lex < kwd_function >(true)) { block->append(parse_definition(Definition::FUNCTION)); } - - // ignore the @charset directive for now - else if (lex< kwd_charset_directive >(true)) { parse_charset_directive(); } - - else if (lex < exactly < else_kwd >>(true)) { error("Invalid CSS: @else must come after @if"); } - - // generic at keyword (keep last) - else if (lex< at_keyword >(true)) { block->append(parse_directive()); } + // Consumes a plain CSS string. This returns the parsed contents of the + // string—that is, it doesn't include quotes and its escapes are resolved. + sass::string Parser::string() + { + // NOTE: this logic is largely duplicated in ScssParser._interpolatedString. + // Most changes here should be mirrored there. - else if (is_root && stack.back() != Scope::AtRoot /* && block->is_root() */) { - lex< css_whitespace >(); - if (position >= end) return true; - css_error("Invalid CSS", " after ", ": expected 1 selector or at-rule, was "); - } - // parse a declaration - else - { - // ToDo: how does it handle parse errors? - // maybe we are expected to parse something? - Declaration_Obj decl = parse_declaration(); - decl->tabs(indentation); - block->append(decl); - // maybe we have a "sub-block" - if (peek< exactly<'{'> >()) { - if (decl->is_indented()) ++ indentation; - // parse a propset that rides on the declaration's property - stack.push_back(Scope::Properties); - decl->block(parse_block()); - stack.pop_back(); - if (decl->is_indented()) -- indentation; - } + uint8_t quote = scanner.peekChar(); + if (quote != $apos && quote != $quote) { + error("Expected string.", + scanner.rawSpan()); + /*, + position: quote == null ? scanner.position : scanner.position - 1*/ } - // something matched - return true; - } - // EO parse_block_nodes + scanner.readChar(); - // parse imports inside the - Import_Obj Parser::parse_import() - { - Import_Obj imp = SASS_MEMORY_NEW(Import, pstate); - sass::vector> to_import; - bool first = true; - do { - while (lex< block_comment >()); - if (lex< quoted_string >()) { - to_import.push_back(std::pair(sass::string(lexed), {})); + StringBuffer buffer; + while (true) { + uint8_t next = scanner.peekChar(); + if (next == quote) { + scanner.readChar(); + break; } - else if (lex< uri_prefix >()) { - Arguments_Obj args = SASS_MEMORY_NEW(Arguments, pstate); - Function_Call_Obj result = SASS_MEMORY_NEW(Function_Call, pstate, sass::string("url"), args); - - if (lex< quoted_string >()) { - ExpressionObj quoted_url = parse_string(); - args->append(SASS_MEMORY_NEW(Argument, quoted_url->pstate(), quoted_url)); - } - else if (String_Obj string_url = parse_url_function_argument()) { - args->append(SASS_MEMORY_NEW(Argument, string_url->pstate(), string_url)); - } - else if (peek < skip_over_scopes < exactly < '(' >, exactly < ')' > > >(position)) { - ExpressionObj braced_url = parse_list(); // parse_interpolated_chunk(lexed); - args->append(SASS_MEMORY_NEW(Argument, braced_url->pstate(), braced_url)); + else if (next == $nul || isNewline(next)) { + sass::sstream strm; + strm << "Expected " << quote << "."; + error(strm.str(), + scanner.rawSpan()); + } + else if (next == $backslash) { + if (isNewline(scanner.peekChar(1))) { + scanner.readChar(); + scanner.readChar(); } else { - error("malformed URL"); + buffer.writeCharCode(escapeCharacter()); } - if (!lex< exactly<')'> >()) error("URI is missing ')'"); - to_import.push_back(std::pair("", result)); } else { - if (first) error("@import directive requires a url or quoted path"); - else error("expecting another url or quoted path in @import list"); - } - first = false; - } while (lex_css< exactly<','> >()); - - if (!peek_css< alternatives< exactly<';'>, exactly<'}'>, end_of_file > >()) { - List_Obj import_queries = parse_media_queries(); - imp->import_queries(import_queries); - } - - for(auto location : to_import) { - if (location.second) { - imp->urls().push_back(location.second); - } - // check if custom importers want to take over the handling - else if (!ctx.call_importers(unquote(location.first), getPath(), pstate, imp)) { - // nobody wants it, so we do our import - ctx.import_url(imp, location.first, getPath()); + buffer.write(scanner.readChar()); } } - return imp; - } - - Definition_Obj Parser::parse_definition(Definition::Type which_type) - { - sass::string which_str(lexed); - if (!lex< identifier >()) error("invalid name in " + which_str + " definition"); - sass::string name(Util::normalize_underscores(lexed)); - if (which_type == Definition::FUNCTION && (name == "and" || name == "or" || name == "not")) - { error("Invalid function name \"" + name + "\"."); } - SourceSpan source_position_of_def = pstate; - Parameters_Obj params = parse_parameters(); - if (which_type == Definition::MIXIN) stack.push_back(Scope::Mixin); - else stack.push_back(Scope::Function); - Block_Obj body = parse_block(); - stack.pop_back(); - return SASS_MEMORY_NEW(Definition, source_position_of_def, name, params, body, which_type); - } - - Parameters_Obj Parser::parse_parameters() - { - Parameters_Obj params = SASS_MEMORY_NEW(Parameters, pstate); - if (lex_css< exactly<'('> >()) { - // if there's anything there at all - if (!peek_css< exactly<')'> >()) { - do { - if (peek< exactly<')'> >()) break; - params->append(parse_parameter()); - } while (lex_css< exactly<','> >()); - } - if (!lex_css< exactly<')'> >()) { - css_error("Invalid CSS", " after ", ": expected \")\", was "); - } - } - return params; + return buffer.buffer; } - Parameter_Obj Parser::parse_parameter() + // Consumes and returns a natural number. + // That is, a non - negative integer. + // Doesn't support scientific notation. + double Parser::naturalNumber() { - if (peek< alternatives< exactly<','>, exactly< '{' >, exactly<';'> > >()) { - css_error("Invalid CSS", " after ", ": expected variable (e.g. $foo), was "); + if (!isDigit(scanner.peekChar())) { + error("Expected digit.", + scanner.rawSpan()); } - while (lex< alternatives < spaces, block_comment > >()); - lex < variable >(); - sass::string name(Util::normalize_underscores(lexed)); - SourceSpan pos = pstate; - ExpressionObj val; - bool is_rest = false; - while (lex< alternatives < spaces, block_comment > >()); - if (lex< exactly<':'> >()) { // there's a default value - while (lex< block_comment >()); - val = parse_space_list(); + uint8_t first = scanner.readChar(); + double number = asDecimal(first); + while (isDigit(scanner.peekChar())) { + number = asDecimal(scanner.readChar()) + + number * 10; } - else if (lex< exactly< ellipsis > >()) { - is_rest = true; - } - return SASS_MEMORY_NEW(Parameter, pos, name, val, is_rest); - } - - Arguments_Obj Parser::parse_arguments() - { - Arguments_Obj args = SASS_MEMORY_NEW(Arguments, pstate); - if (lex_css< exactly<'('> >()) { - // if there's anything there at all - if (!peek_css< exactly<')'> >()) { - do { - if (peek< exactly<')'> >()) break; - args->append(parse_argument()); - } while (lex_css< exactly<','> >()); - } - if (!lex_css< exactly<')'> >()) { - css_error("Invalid CSS", " after ", ": expected expression (e.g. 1px, bold), was "); - } - } - return args; + return number; } + // EO naturalNumber - Argument_Obj Parser::parse_argument() + // Consumes tokens until it reaches a top-level `";"`, `")"`, `"]"`, + // or `"}"` and returns their contents as a string. If [allowEmpty] + // is `false` (the default), this requires at least one token. + sass::string Parser::declarationValue(bool allowEmpty) { - if (peek< alternatives< exactly<','>, exactly< '{' >, exactly<';'> > >()) { - css_error("Invalid CSS", " after ", ": expected \")\", was "); - } - if (peek_css< sequence < exactly< hash_lbrace >, exactly< rbrace > > >()) { - position += 2; - css_error("Invalid CSS", " after ", ": expected expression (e.g. 1px, bold), was "); - } + // NOTE: this logic is largely duplicated in + // StylesheetParser.parseInterpolatedDeclarationValue. + // Most changes here should be mirrored there. - Argument_Obj arg; - if (peek_css< sequence < variable, optional_css_comments, exactly<':'> > >()) { - lex_css< variable >(); - sass::string name(Util::normalize_underscores(lexed)); - SourceSpan p = pstate; - lex_css< exactly<':'> >(); - ExpressionObj val = parse_space_list(); - arg = SASS_MEMORY_NEW(Argument, p, val, name); - } - else { - bool is_arglist = false; - bool is_keyword = false; - ExpressionObj val = parse_space_list(); - List* l = Cast(val); - if (lex_css< exactly< ellipsis > >()) { - if (val->concrete_type() == Expression::MAP || ( - (l != NULL && l->separator() == SASS_HASH) - )) is_keyword = true; - else is_arglist = true; - } - arg = SASS_MEMORY_NEW(Argument, pstate, val, "", is_arglist, is_keyword); - } - return arg; - } + sass::string url; + StringBuffer buffer; + bool wroteNewline = false; + sass::vector brackets; - Assignment_Obj Parser::parse_assignment() - { - sass::string name(Util::normalize_underscores(lexed)); - SourceSpan var_source_position = pstate; - if (!lex< exactly<':'> >()) error("expected ':' after " + name + " in assignment statement"); - if (peek_css< alternatives < exactly<';'>, end_of_file > >()) { - css_error("Invalid CSS", " after ", ": expected expression (e.g. 1px, bold), was "); - } - ExpressionObj val; - Lookahead lookahead = lookahead_for_value(position); - if (lookahead.has_interpolants && lookahead.found) { - val = parse_value_schema(lookahead.found); - } else { - val = parse_list(); - } - bool is_default = false; - bool is_global = false; - while (peek< alternatives < default_flag, global_flag > >()) { - if (lex< default_flag >()) is_default = true; - else if (lex< global_flag >()) is_global = true; - } - return SASS_MEMORY_NEW(Assignment, var_source_position, name, val, is_default, is_global); - } + while (true) { + uint8_t next = scanner.peekChar(); + switch (next) { + case $backslash: + escape(buffer, true); + wroteNewline = false; + break; - // a ruleset connects a selector and a block - StyleRuleObj Parser::parse_ruleset(Lookahead lookahead) - { - NESTING_GUARD(nestings); - // inherit is_root from parent block - Block_Obj parent = block_stack.back(); - bool is_root = parent && parent->is_root(); - // make sure to move up the the last position - lex < optional_css_whitespace >(false, true); - // create the connector object (add parts later) - StyleRuleObj ruleset = SASS_MEMORY_NEW(StyleRule, pstate); - // parse selector static or as schema to be evaluated later - if (lookahead.parsable) { - ruleset->selector(parseSelectorList(false)); - } - else { - SelectorListObj list = SASS_MEMORY_NEW(SelectorList, pstate); - auto sc = parse_selector_schema(lookahead.position, false); - ruleset->schema(sc); - ruleset->selector(list); - } - // then parse the inner block - stack.push_back(Scope::Rules); - ruleset->block(parse_block()); - stack.pop_back(); - // update for end position - ruleset->update_pstate(pstate); - ruleset->block()->update_pstate(pstate); - // need this info for coherence checks - ruleset->is_root(is_root); - // return AST Node - return ruleset; - } + case $quote: + case $apos: + buffer.write(rawText(&Parser::string)); + wroteNewline = false; + break; - // parse a selector schema that will be evaluated in the eval stage - // uses a string schema internally to do the actual schema handling - // in the eval stage we will be re-parse it into an actual selector - Selector_Schema_Obj Parser::parse_selector_schema(const char* end_of_selector, bool chroot) - { - NESTING_GUARD(nestings); - // move up to the start - lex< optional_spaces >(); - const char* i = position; - // selector schema re-uses string schema implementation - String_Schema* schema = SASS_MEMORY_NEW(String_Schema, pstate); - // the selector schema is pretty much just a wrapper for the string schema - Selector_Schema_Obj selector_schema = SASS_MEMORY_NEW(Selector_Schema, pstate, schema); - selector_schema->connect_parent(chroot == false); - - // process until end - while (i < end_of_selector) { - // try to parse multiple interpolants - if (const char* p = find_first_in_interval< exactly, block_comment >(i, end_of_selector)) { - // accumulate the preceding segment if the position has advanced - if (i < p) { - sass::string parsed(i, p); - String_Constant_Obj str = SASS_MEMORY_NEW(String_Constant, pstate, parsed); - pstate.position += Offset(parsed); - str->update_pstate(pstate); - schema->append(str); + case $slash: + if (scanner.peekChar(1) == $asterisk) { + buffer.write(rawText(&Parser::scanLoudComment)); } + else { + buffer.write(scanner.readChar()); + } + wroteNewline = false; + break; - // skip over all nested inner interpolations up to our own delimiter - const char* j = skip_over_scopes< exactly, exactly >(p + 2, end_of_selector); - // check if the interpolation never ends of only contains white-space (error out) - if (!j || peek < sequence < optional_spaces, exactly > >(p+2)) { - position = p+2; - css_error("Invalid CSS", " after ", ": expected expression (e.g. 1px, bold), was "); + case $space: + case $tab: + if (wroteNewline || !isWhitespace(scanner.peekChar(1))) { + buffer.write($space); } - // pass inner expression to the parser to resolve nested interpolations - LocalOption partEnd(end, j); - LocalOption partBeg(position, p + 2); - ExpressionObj interpolant = parse_list(); - // set status on the list expression - interpolant->is_interpolant(true); - // schema->has_interpolants(true); - // add to the string schema - schema->append(interpolant); - // advance parser state - pstate.position.add(p+2, j); - // advance position - i = j; - } - // no more interpolants have been found - // add the last segment if there is one - else { - // make sure to add the last bits of the string up to the end (if any) - if (i < end_of_selector) { - sass::string parsed(i, end_of_selector); - String_Constant_Obj str = SASS_MEMORY_NEW(String_Constant, pstate, parsed); - pstate.position += Offset(parsed); - str->update_pstate(pstate); - i = end_of_selector; - schema->append(str); + scanner.readChar(); + break; + + case $lf: + case $cr: + case $ff: + if (!isNewline(scanner.peekChar(-1))) { + buffer.write("\n"); } - // exit loop - } - } - // EO until eos + scanner.readChar(); + wroteNewline = true; + break; - // update position - position = i; + case $lparen: + case $lbrace: + case $lbracket: + buffer.write(next); + brackets.emplace_back(opposite(scanner.readChar())); + wroteNewline = false; + break; - // update for end position - selector_schema->update_pstate(pstate); - schema->update_pstate(pstate); + case $rparen: + case $rbrace: + case $rbracket: + if (brackets.empty()) goto outOfLoop; + buffer.write(next); + scanner.expectChar(brackets.back()); + wroteNewline = false; + brackets.pop_back(); + break; - after_token = before_token = pstate.position; + case $semicolon: + if (brackets.empty()) goto outOfLoop; + buffer.write(scanner.readChar()); + break; - // return parsed result - return selector_schema.detach(); - } - // EO parse_selector_schema + case $u: + case $U: + url = tryUrl() ; + if (!url.empty()) { + buffer.write(url); + } + else { + buffer.write(scanner.readChar()); + } + wroteNewline = false; + break; - void Parser::parse_charset_directive() - { - lex < - sequence < - quoted_string, - optional_spaces, - exactly <';'> - > - >(); - } + default: + if (next == $nul) goto outOfLoop; - // called after parsing `kwd_include_directive` - Mixin_Call_Obj Parser::parse_include_directive() - { - // lex identifier into `lexed` var - lex_identifier(); // may error out - // normalize underscores to hyphens - sass::string name(Util::normalize_underscores(lexed)); - // create the initial mixin call object - Mixin_Call_Obj call = SASS_MEMORY_NEW(Mixin_Call, pstate, name, Arguments_Obj{}); - // parse mandatory arguments - call->arguments(parse_arguments()); - // parse using and optional block parameters - bool has_parameters = lex< kwd_using >() != nullptr; - - if (has_parameters) { - if (!peek< exactly<'('> >()) css_error("Invalid CSS", " after ", ": expected \"(\", was "); - } else { - if (peek< exactly<'('> >()) css_error("Invalid CSS", " after ", ": expected \";\", was "); + if (lookingAtIdentifier()) { + buffer.write(readIdentifier()); + } + else { + buffer.write(scanner.readChar()); + } + wroteNewline = false; + break; + } } - if (has_parameters) call->block_parameters(parse_parameters()); - - // parse optional block - if (peek < exactly <'{'> >()) { - call->block(parse_block()); - } - else if (has_parameters) { - css_error("Invalid CSS", " after ", ": expected \"{\", was "); - } - // return ast node - return call.detach(); - } - // EO parse_include_directive + outOfLoop: + if (!brackets.empty()) scanner.expectChar(brackets.back()); + if (!allowEmpty && buffer.empty()) error( + "Expected token.", scanner.rawSpan()); + return buffer.buffer; - SimpleSelectorObj Parser::parse_simple_selector() - { - lex < css_comments >(false); - if (lex< class_name >()) { - return SASS_MEMORY_NEW(ClassSelector, pstate, lexed); - } - else if (lex< id_name >()) { - return SASS_MEMORY_NEW(IDSelector, pstate, lexed); - } - else if (lex< alternatives < variable, number, static_reference_combinator > >()) { - return SASS_MEMORY_NEW(TypeSelector, pstate, lexed); - } - else if (peek< pseudo_not >()) { - return parse_negated_selector2(); - } - else if (peek< re_pseudo_selector >()) { - return parse_pseudo_selector(); - } - else if (peek< exactly<':'> >()) { - return parse_pseudo_selector(); - } - else if (lex < exactly<'['> >()) { - return parse_attribute_selector(); - } - else if (lex< placeholder >()) { - return SASS_MEMORY_NEW(PlaceholderSelector, pstate, lexed); - } - else { - css_error("Invalid CSS", " after ", ": expected selector, was "); - } - // failed - return {}; } + // EO declarationValue - PseudoSelectorObj Parser::parse_negated_selector2() + // Consumes a `url()` token if possible, and returns `null` otherwise. + sass::string Parser::tryUrl() { - lex< pseudo_not >(); - sass::string name(lexed); - SourceSpan nsource_position = pstate; - SelectorListObj negated = parseSelectorList(true); - if (!lex< exactly<')'> >()) { - error("negated selector is missing ')'"); - } - name.erase(name.size() - 1); - PseudoSelector* sel = SASS_MEMORY_NEW(PseudoSelector, nsource_position, name.substr(1)); - sel->selector(negated); - return sel; - } + // NOTE: this logic is largely duplicated in ScssParser.tryUrlContents. + // Most changes here should be mirrored there. - // Helper to clean binominal string - bool BothAreSpaces(char lhs, char rhs) { return isspace(lhs) && isspace(rhs); } - - // a pseudo selector often starts with one or two colons - // it can contain more selectors inside parentheses - SimpleSelectorObj Parser::parse_pseudo_selector() { - - // Lex one or two colon characters - if (lex()) { - sass::string colons(lexed); - // Check if it is a pseudo element - bool element = colons.size() == 2; - - if (lex< sequence< - // we keep the space within the name, strange enough - // ToDo: refactor output to schedule the space for it - // or do we really want to keep the real white-space? - sequence< identifier, optional < block_comment >, exactly<'('> > - > >()) - { - - sass::string name(lexed); - name.erase(name.size() - 1); - SourceSpan p = pstate; - - // specially parse nth-child pseudo selectors - if (lex_css < sequence < binomial, word_boundary >>()) { - sass::string parsed(lexed); // always compacting binominals (as dart-sass) - parsed.erase(std::unique(parsed.begin(), parsed.end(), BothAreSpaces), parsed.end()); - String_Constant_Obj arg = SASS_MEMORY_NEW(String_Constant, pstate, parsed); - PseudoSelector* pseudo = SASS_MEMORY_NEW(PseudoSelector, p, name, element); - if (lex < sequence < css_whitespace, insensitive < of_kwd >>>(false)) { - pseudo->selector(parseSelectorList(true)); - } - pseudo->argument(arg); - if (lex_css< exactly<')'> >()) { - return pseudo; - } - } - else { - if (peek_css< exactly<')'>>() && Util::equalsLiteral("nth-", name.substr(0, 4))) { - css_error("Invalid CSS", " after ", ": expected An+B expression, was "); - } - - sass::string unvendored = Util::unvendor(name); - - if (unvendored == "not" || unvendored == "matches" || unvendored == "current" || unvendored == "any" || unvendored == "has" || unvendored == "host" || unvendored == "host-context" || unvendored == "slotted") { - if (SelectorListObj wrapped = parseSelectorList(true)) { - if (wrapped && lex_css< exactly<')'> >()) { - PseudoSelector* pseudo = SASS_MEMORY_NEW(PseudoSelector, p, name, element); - pseudo->selector(wrapped); - return pseudo; - } - } - } else { - String_Schema_Obj arg = parse_css_variable_value(); - PseudoSelector* pseudo = SASS_MEMORY_NEW(PseudoSelector, p, name, element); - pseudo->argument(arg); - - if (lex_css< exactly<')'> >()) { - return pseudo; - } - } - } + StringScannerState state = scanner.state(); + if (!scanIdentifier("url")) return ""; - } - // EO if pseudo selector + if (!scanner.scanChar($lparen)) { + scanner.backtrack(state); + return ""; + } + + scanWhitespace(); - else if (lex < sequence< optional < pseudo_prefix >, identifier > >()) { - return SASS_MEMORY_NEW(PseudoSelector, pstate, lexed, element); + // Match Ruby Sass's behavior: parse a raw URL() if possible, + // and if not backtrack and re-parse as a function expression. + uint8_t next; + StringBuffer buffer; + buffer.write("url("); + while (true) { + if (!scanner.peekChar(next)) { + break; } - else if (lex < pseudo_prefix >()) { - css_error("Invalid CSS", " after ", ": expected pseudoclass or pseudoelement, was "); + if (next == $percent || + next == $ampersand || + next == $hash || + (next >= $asterisk && next <= $tilde) || + next >= 0x0080) { + buffer.write(scanner.readChar()); + } + else if (next == $backslash) { + escape(buffer); + } + else if (isWhitespace(next)) { + scanWhitespace(); + if (scanner.peekChar() != $rparen) break; + } + else if (next == $rparen) { + buffer.write(scanner.readChar()); + return buffer.buffer; + } + else { + break; } - - } - else { - lex < identifier >(); // needed for error message? - css_error("Invalid CSS", " after ", ": expected selector, was "); } - - css_error("Invalid CSS", " after ", ": expected \")\", was "); - - // unreachable statement - return {}; - } - - const char* Parser::re_attr_sensitive_close(const char* src) - { - return alternatives < exactly<']'>, exactly<'/'> >(src); + scanner.backtrack(state); + return ""; } + // EO tryUrl - const char* Parser::re_attr_insensitive_close(const char* src) + // Consumes a Sass variable name, and returns + // its name without the dollar sign. + sass::string Parser::variableName() { - return sequence < insensitive<'i'>, re_attr_sensitive_close >(src); + scanner.expectChar($dollar); + // dart sass removes the dollar + return readIdentifier(); } - AttributeSelectorObj Parser::parse_attribute_selector() + // Consumes an escape sequence and returns the text that defines it. + // If [identifierStart] is true, this normalizes the escape sequence + // as though it were at the beginning of an identifier. + void Parser::escape(StringBuffer& buffer, bool identifierStart) { - SourceSpan p = pstate; - if (!lex_css< attribute_name >()) error("invalid attribute name in attribute selector"); - sass::string name(lexed); - if (lex_css< re_attr_sensitive_close >()) { - return SASS_MEMORY_NEW(AttributeSelector, p, name, "", String_Obj{}); + Offset start(scanner.offset); + scanner.expectChar($backslash); + uint32_t value = 0; + uint8_t first, next; + if (!scanner.peekChar(first)) { + error("Expected escape sequence.", + scanner.rawSpan()); + return; } - else if (lex_css< re_attr_insensitive_close >()) { - char modifier = lexed.begin[0]; - return SASS_MEMORY_NEW(AttributeSelector, p, name, "", String_Obj{}, modifier); + else if (isNewline(first)) { + error("Expected escape sequence.", + scanner.rawSpan()); + return; } - if (!lex_css< alternatives< exact_match, class_match, dash_match, - prefix_match, suffix_match, substring_match > >()) { - error("invalid operator in attribute selector for " + name); - } - sass::string matcher(lexed); + else if (isHex(first)) { + for (uint8_t i = 0; i < 6; i++) { + if (!scanner.peekChar(next)) break; + if (next == $nul || !isHex(next)) break; + value *= 16; + value += asHex(scanner.readChar()); + } - String_Obj value; - if (lex_css< identifier >()) { - value = SASS_MEMORY_NEW(String_Constant, p, lexed); - } - else if (lex_css< quoted_string >()) { - value = parse_interpolated_chunk(lexed, true); // needed! + scanCharIf(isWhitespace); } else { - error("expected a string constant or identifier in attribute selector for " + name); + value = scanner.readChar(); } - if (lex_css< re_attr_sensitive_close >()) { - return SASS_MEMORY_NEW(AttributeSelector, p, name, matcher, value, 0); + if (identifierStart ? isNameStart(value) : isName(value)) { + if (!utf8::internal::is_code_point_valid(value)) { + error("Invalid Unicode code point.", + scanner.relevantSpanFrom(start)); + } + buffer.writeCharCode(value); + return; } - else if (lex_css< re_attr_insensitive_close >()) { - char modifier = lexed.begin[0]; - return SASS_MEMORY_NEW(AttributeSelector, p, name, matcher, value, modifier); + else if (value <= 0x1F || + value == 0x7F || + (identifierStart && isDigit(value))) { + buffer.write($backslash); + if (value > 0xF) buffer.write(hexCharFor(value >> 4)); + buffer.write(hexCharFor(value & 0xF)); + buffer.write($space); + return; + } + else { + buffer.write($backslash); + buffer.writeCharCode(value); + return; } - error("unterminated attribute selector for " + name); - return {}; // to satisfy compilers (error must not return) } + // EO tryUrl - /* parse block comment and add to block */ - void Parser::parse_block_comments(bool store) + // Consumes an escape sequence and returns the character it represents. + uint32_t Parser::escapeCharacter() { - Block_Obj block = block_stack.back(); + // See https://drafts.csswg.org/css-syntax-3/#consume-escaped-code-point. - while (lex< block_comment >()) { - bool is_important = lexed.begin[2] == '!'; - // flag on second param is to skip loosely over comments - String_Obj contents = parse_interpolated_chunk(lexed, true, false); - if (store) block->append(SASS_MEMORY_NEW(Comment, pstate, contents, is_important)); - } - } - - Declaration_Obj Parser::parse_declaration() { - String_Obj prop; - bool is_custom_property = false; - if (lex< sequence< optional< exactly<'*'> >, identifier_schema > >()) { - const sass::string property(lexed); - is_custom_property = property.compare(0, 2, "--") == 0; - prop = parse_identifier_schema(); - } - else if (lex< sequence< optional< exactly<'*'> >, identifier, zero_plus< block_comment > > >()) { - const sass::string property(lexed); - is_custom_property = property.compare(0, 2, "--") == 0; - prop = SASS_MEMORY_NEW(String_Constant, pstate, lexed); + uint8_t first, next; + scanner.expectChar($backslash); + if (!scanner.peekChar(first)) { + return 0xFFFD; } - else { - css_error("Invalid CSS", " after ", ": expected \"}\", was "); - } - bool is_indented = true; - const sass::string property(lexed); - if (!lex_css< one_plus< exactly<':'> > >()) error("property \"" + escape_string(property) + "\" must be followed by a ':'"); - if (!is_custom_property && match< sequence< optional_css_comments, exactly<';'> > >()) error("style declaration must contain a value"); - if (match< sequence< optional_css_comments, exactly<'{'> > >()) is_indented = false; // don't indent if value is empty - if (is_custom_property) { - return SASS_MEMORY_NEW(Declaration, prop->pstate(), prop, parse_css_variable_value(), false, true); + else if (isNewline(first)) { + error("Expected escape sequence.", + scanner.rawSpan()); + return 0; } - lex < css_comments >(false); - if (peek_css< static_value >()) { - return SASS_MEMORY_NEW(Declaration, prop->pstate(), prop, parse_static_value()/*, lex()*/); - } - else { - ExpressionObj value; - Lookahead lookahead = lookahead_for_value(position); - if (lookahead.found) { - if (lookahead.has_interpolants) { - value = parse_value_schema(lookahead.found); - } else { - value = parse_list(DELAYED); - } + else if (isHex(first)) { + uint32_t value = 0; + for (uint8_t i = 0; i < 6; i++) { + if (!scanner.peekChar(next) || !isHex(next)) break; + value = (value << 4) + asHex(scanner.readChar()); + } + if (isWhitespace(scanner.peekChar())) { + scanner.readChar(); + } + if (value == 0 || + (value >= 0xD800 && value <= 0xDFFF) || + value >= 0x10FFFF) { + return 0xFFFD; } else { - value = parse_list(DELAYED); - if (List* list = Cast(value)) { - if (!list->is_bracketed() && list->length() == 0 && !peek< exactly <'{'> >()) { - css_error("Invalid CSS", " after ", ": expected expression (e.g. 1px, bold), was "); - } - } + return value; } - lex < css_comments >(false); - Declaration_Obj decl = SASS_MEMORY_NEW(Declaration, prop->pstate(), prop, value/*, lex()*/); - decl->is_indented(is_indented); - decl->update_pstate(pstate); - return decl; + } + else { + return scanner.readChar(); } } - ExpressionObj Parser::parse_map() + // Consumes the next character if it matches [condition]. + // Returns whether or not the character was consumed. + bool Parser::scanCharIf(bool(*condition)(uint8_t character)) { - NESTING_GUARD(nestings); - ExpressionObj key = parse_list(); - List_Obj map = SASS_MEMORY_NEW(List, pstate, 0, SASS_HASH); - - // it's not a map so return the lexed value as a list value - if (!lex_css< exactly<':'> >()) - { return key; } + uint8_t next = scanner.peekChar(); + if (!condition(next)) return false; + scanner.readChar(); + return true; + } - List_Obj l = Cast(key); - if (l && l->separator() == SASS_COMMA) { - css_error("Invalid CSS", " after ", ": expected \")\", was "); + bool Parser::scanIdentCharSensitive(uint8_t letter) + { + auto next = scanner.peekChar(); + if (letter == next) { + scanner.readChar(); + return true; } - - ExpressionObj value = parse_space_list(); - - map->append(key); - map->append(value); - - while (lex_css< exactly<','> >()) - { - // allow trailing commas - #495 - if (peek_css< exactly<')'> >(position)) - { break; } - - key = parse_space_list(); - - if (!(lex< exactly<':'> >())) - { css_error("Invalid CSS", " after ", ": expected \":\", was "); } - - value = parse_space_list(); - - map->append(key); - map->append(value); + else if (next == $backslash) { + StringScannerState state = scanner.state(); + next = escapeCharacter(); + if (letter == next) return true; + scanner.backtrack(state); } - - SourceSpan ps = map->pstate(); - ps.offset = pstate.position - ps.position + pstate.offset; - map->pstate(ps); - - return map; + return false; } - ExpressionObj Parser::parse_bracket_list() + bool Parser::scanIdentCharInsensitive(uint8_t letter) { - NESTING_GUARD(nestings); - // check if we have an empty list - // return the empty list as such - if (peek_css< list_terminator >(position)) - { - // return an empty list (nothing to delay) - return SASS_MEMORY_NEW(List, pstate, 0, SASS_SPACE, false, true); + auto next = scanner.peekChar(); + if (equalsLetterIgnoreCase(letter, next)) { + scanner.readChar(); + return true; } - - bool has_paren = peek_css< exactly<'('> >() != NULL; - - // now try to parse a space list - ExpressionObj list = parse_space_list(); - // if it's a singleton, return it (don't wrap it) - if (!peek_css< exactly<','> >(position)) { - List_Obj l = Cast(list); - if (!l || l->is_bracketed() || has_paren) { - List_Obj bracketed_list = SASS_MEMORY_NEW(List, pstate, 1, SASS_SPACE, false, true); - bracketed_list->append(list); - return bracketed_list; - } - l->is_bracketed(true); - return l; + else if (next == $backslash) { + StringScannerState state = scanner.state(); + next = escapeCharacter(); + if (equalsLetterIgnoreCase(letter, next)) return true; + scanner.backtrack(state); } + return false; + } - // if we got so far, we actually do have a comma list - List_Obj bracketed_list = SASS_MEMORY_NEW(List, pstate, 2, SASS_COMMA, false, true); - // wrap the first expression - bracketed_list->append(list); - - while (lex_css< exactly<','> >()) - { - // check for abort condition - if (peek_css< list_terminator >(position) - ) { break; } - // otherwise add another expression - bracketed_list->append(parse_space_list()); - } - // return the list - return bracketed_list; + void Parser::expectIdentCharSensitive(uint8_t letter) + { + Offset start(scanner.offset); + if (scanIdentCharSensitive(letter)) return; + + sass::string msg = "Expected \""; + msg += letter; msg += "\"."; + scanner.offset = start; + error(msg, scanner.rawSpan()); } - // parse list returns either a space separated list, - // a comma separated list or any bare expression found. - // so to speak: we unwrap items from lists if possible here! - ExpressionObj Parser::parse_list(bool delayed) + void Parser::expectIdentCharInsensitive(uint8_t letter) { - NESTING_GUARD(nestings); - return parse_comma_list(delayed); + Offset start(scanner.offset); + if (scanIdentCharInsensitive(letter)) return; + + sass::string msg = "Expected \""; + msg += letter; msg += "\"."; + scanner.offset = start; + error(msg, scanner.rawSpan()); } - // will return singletons unwrapped - ExpressionObj Parser::parse_comma_list(bool delayed) + + // Returns whether the scanner is immediately before a number. This follows [the CSS algorithm]. + // [the CSS algorithm]: https://drafts.csswg.org/css-syntax-3/#starts-with-a-number + bool Parser::lookingAtNumber() const { - NESTING_GUARD(nestings); - // check if we have an empty list - // return the empty list as such - if (peek_css< list_terminator >(position)) - { - // return an empty list (nothing to delay) - return SASS_MEMORY_NEW(List, pstate, 0); - } + uint8_t first, second, third; + if (!scanner.peekChar(first)) return false; + if (isDigit(first)) return true; - // now try to parse a space list - ExpressionObj list = parse_space_list(); - // if it's a singleton, return it (don't wrap it) - if (!peek_css< exactly<','> >(position)) { - // set_delay doesn't apply to list children - // so this will only undelay single values - if (!delayed) list->set_delayed(false); - return list; + if (first == $dot) { + return scanner.peekChar(second, 1) + && isDigit(second); } + else if (first == $plus || first == $minus) { + if (!scanner.peekChar(second, 1)) return false; + if (isDigit(second)) return true; + if (second != $dot) return false; - // if we got so far, we actually do have a comma list - List_Obj comma_list = SASS_MEMORY_NEW(List, pstate, 2, SASS_COMMA); - // wrap the first expression - comma_list->append(list); - - while (lex_css< exactly<','> >()) - { - // check for abort condition - if (peek_css< list_terminator >(position) - ) { break; } - // otherwise add another expression - comma_list->append(parse_space_list()); + return scanner.peekChar(third, 2) + && isDigit(third); } - // return the list - return comma_list; - } - // EO parse_comma_list - - // will return singletons unwrapped - ExpressionObj Parser::parse_space_list() - { - NESTING_GUARD(nestings); - ExpressionObj disj1 = parse_disjunction(); - // if it's a singleton, return it (don't wrap it) - if (peek_css< space_list_terminator >(position) - ) { - return disj1; } - - List_Obj space_list = SASS_MEMORY_NEW(List, pstate, 2, SASS_SPACE); - space_list->append(disj1); - - while ( - !(peek_css< space_list_terminator >(position)) && - peek_css< optional_css_whitespace >() != end - ) { - // the space is parsed implicitly? - space_list->append(parse_disjunction()); + else { + return false; } - // return the list - return space_list; } - // EO parse_space_list + // EO lookingAtNumber - // parse logical OR operation - ExpressionObj Parser::parse_disjunction() + // Returns whether the scanner is immediately before a plain CSS identifier. + // If [forward] is passed, this looks that many characters forward instead. + // This is based on [the CSS algorithm][], but it assumes all backslashes start escapes. + // [the CSS algorithm]: https://drafts.csswg.org/css-syntax-3/#would-start-an-identifier + bool Parser::lookingAtIdentifier(size_t forward) const { - NESTING_GUARD(nestings); - advanceToNextToken(); - SourceSpan state(pstate); - // parse the left hand side conjunction - ExpressionObj conj = parse_conjunction(); - // parse multiple right hand sides - sass::vector operands; - while (lex_css< kwd_or >()) - operands.push_back(parse_conjunction()); - // if it's a singleton, return it directly - if (operands.size() == 0) return conj; - // fold all operands into one binary expression - ExpressionObj ex = fold_operands(conj, operands, { Sass_OP::OR }); - state.offset = pstate.position - state.position + pstate.offset; - ex->pstate(state); - return ex; - } - // EO parse_disjunction + // See also [ScssParser.lookingAtInterpolatedIdentifier]. + uint8_t first, second; + if (!scanner.peekChar(first, forward)) return false; + if (isNameStart(first) || first == $backslash) return true; + if (first != $minus) return false; - // parse logical AND operation - ExpressionObj Parser::parse_conjunction() - { - NESTING_GUARD(nestings); - advanceToNextToken(); - SourceSpan state(pstate); - // parse the left hand side relation - ExpressionObj rel = parse_relation(); - // parse multiple right hand sides - sass::vector operands; - while (lex_css< kwd_and >()) { - operands.push_back(parse_relation()); - } - // if it's a singleton, return it directly - if (operands.size() == 0) return rel; - // fold all operands into one binary expression - ExpressionObj ex = fold_operands(rel, operands, { Sass_OP::AND }); - state.offset = pstate.position - state.position + pstate.offset; - ex->pstate(state); - return ex; + if (!scanner.peekChar(second, forward + 1)) return false; + + return isNameStart(second) + || second == $backslash + || second == $minus; } - // EO parse_conjunction + // EO lookingAtIdentifier - // parse comparison operations - ExpressionObj Parser::parse_relation() + // Returns whether the scanner is immediately before a sequence + // of characters that could be part of a plain CSS identifier body. + bool Parser::lookingAtIdentifierBody() { - NESTING_GUARD(nestings); - advanceToNextToken(); - SourceSpan state(pstate); - // parse the left hand side expression - ExpressionObj lhs = parse_expression(); - sass::vector operands; - sass::vector operators; - // if it's a singleton, return it (don't wrap it) - while (peek< alternatives < - kwd_eq, - kwd_neq, - kwd_gte, - kwd_gt, - kwd_lte, - kwd_lt - > >(position)) - { - // is directly adjancent to expression? - bool left_ws = peek < css_comments >() != NULL; - // parse the operator - enum Sass_OP op - = lex() ? Sass_OP::EQ - : lex() ? Sass_OP::NEQ - : lex() ? Sass_OP::GTE - : lex() ? Sass_OP::LTE - : lex() ? Sass_OP::GT - : lex() ? Sass_OP::LT - // we checked the possibilities on top of fn - : Sass_OP::EQ; - // is directly adjacent to expression? - bool right_ws = peek < css_comments >() != NULL; - operators.push_back({ op, left_ws, right_ws }); - operands.push_back(parse_expression()); - } - // we are called recursively for list, so we first - // fold inner binary expression which has delayed - // correctly set to zero. After folding we also unwrap - // single nested items. So we cannot set delay on the - // returned result here, as we have lost nestings ... - ExpressionObj ex = fold_operands(lhs, operands, operators); - state.offset = pstate.position - state.position + pstate.offset; - ex->pstate(state); - return ex; + uint8_t next = scanner.peekChar(); + return next && (isName(next) || next == $backslash); } - // parse_relation - - // parse expression valid for operations - // called from parse_relation - // called from parse_for_directive - // called from parse_media_expression - // parse addition and subtraction operations - ExpressionObj Parser::parse_expression() + // EO lookingAtIdentifierBody + + // Consumes an identifier if its name exactly matches [text]. + bool Parser::scanIdentifier(const char* text, bool sensitive) { - NESTING_GUARD(nestings); - advanceToNextToken(); - SourceSpan state(pstate); - // parses multiple add and subtract operations - // NOTE: make sure that identifiers starting with - // NOTE: dashes do NOT count as subtract operation - ExpressionObj lhs = parse_operators(); - // if it's a singleton, return it (don't wrap it) - if (!(peek_css< exactly<'+'> >(position) || - // condition is a bit mysterious, but some combinations should not be counted as operations - (peek< no_spaces >(position) && peek< sequence< negate< unsigned_number >, exactly<'-'>, negate< space > > >(position)) || - (peek< sequence< negate< unsigned_number >, exactly<'-'>, negate< unsigned_number > > >(position))) || - peek< sequence < zero_plus < exactly <'-' > >, identifier > >(position)) - { return lhs; } - - sass::vector operands; - sass::vector operators; - bool left_ws = peek < css_comments >() != NULL; - while ( - lex_css< exactly<'+'> >() || - - ( - ! peek_css< sequence < zero_plus < exactly <'-' > >, identifier > >(position) - && lex_css< sequence< negate< digit >, exactly<'-'> > >() - ) - - ) { - - bool right_ws = peek < css_comments >() != NULL; - operators.push_back({ lexed.to_string() == "+" ? Sass_OP::ADD : Sass_OP::SUB, left_ws, right_ws }); - operands.push_back(parse_operators()); - left_ws = peek < css_comments >() != NULL; + if (!lookingAtIdentifier()) return false; + + StringScannerState state = scanner.state(); + for (size_t i = 0; text[i]; i++) { + if (scanIdentChar(text[i], sensitive)) continue; + scanner.backtrack(state); + return false; } - if (operands.size() == 0) return lhs; - ExpressionObj ex = fold_operands(lhs, operands, operators); - state.offset = pstate.position - state.position + pstate.offset; - ex->pstate(state); - return ex; + if (!lookingAtIdentifierBody()) return true; + scanner.backtrack(state); + return false; } + // EO scanIdentifier - // parse addition and subtraction operations - ExpressionObj Parser::parse_operators() + // Consumes an identifier if its name exactly matches [text]. + bool Parser::scanIdentifier(sass::string text, bool sensitive) { - NESTING_GUARD(nestings); - advanceToNextToken(); - SourceSpan state(pstate); - ExpressionObj factor = parse_factor(); - // if it's a singleton, return it (don't wrap it) - sass::vector operands; // factors - sass::vector operators; // ops - // lex operations to apply to lhs - const char* left_ws = peek < css_comments >(); - while (lex_css< class_char< static_ops > >()) { - const char* right_ws = peek < css_comments >(); - switch(*lexed.begin) { - case '*': operators.push_back({ Sass_OP::MUL, left_ws != 0, right_ws != 0 }); break; - case '/': operators.push_back({ Sass_OP::DIV, left_ws != 0, right_ws != 0 }); break; - case '%': operators.push_back({ Sass_OP::MOD, left_ws != 0, right_ws != 0 }); break; - default: throw std::runtime_error("unknown static op parsed"); - } - operands.push_back(parse_factor()); - left_ws = peek < css_comments >(); - } - // operands and operators to binary expression - ExpressionObj ex = fold_operands(factor, operands, operators); - state.offset = pstate.position - state.position + pstate.offset; - ex->pstate(state); - return ex; + return scanIdentifier(text.c_str(), sensitive); } - // EO parse_operators - + // EO scanIdentifier - // called from parse_operators - // called from parse_value_schema - ExpressionObj Parser::parse_factor() + // Consumes an identifier and asserts that its name exactly matches [text]. + void Parser::expectIdentifier(const char* text, sass::string name, bool sensitive) { - NESTING_GUARD(nestings); - lex < css_comments >(false); - if (lex_css< exactly<'('> >()) { - // parse_map may return a list - ExpressionObj value = parse_map(); - // lex the expected closing parenthesis - if (!lex_css< exactly<')'> >()) error("unclosed parenthesis"); - // expression can be evaluated - return value; - } - else if (lex_css< exactly<'['> >()) { - // explicit bracketed - ExpressionObj value = parse_bracket_list(); - // lex the expected closing square bracket - if (!lex_css< exactly<']'> >()) error("unclosed squared bracket"); - return value; - } - // string may be interpolated - // if (lex< quoted_string >()) { - // return &parse_string(); - // } - else if (peek< ie_property >()) { - return parse_ie_property(); - } - else if (peek< ie_keyword_arg >()) { - return parse_ie_keyword_arg(); - } - else if (peek< sequence < calc_fn_call, exactly <'('> > >()) { - return parse_calc_function(); - } - else if (lex < functional_schema >()) { - return parse_function_call_schema(); - } - else if (lex< identifier_schema >()) { - String_Obj string = parse_identifier_schema(); - if (String_Schema* schema = Cast(string)) { - if (lex < exactly < '(' > >()) { - schema->append(parse_list()); - lex < exactly < ')' > >(); - } - } - return string; - } - else if (peek< sequence< uri_prefix, W, real_uri_value > >()) { - return parse_url_function_string(); - } - else if (peek< re_functional >()) { - return parse_function_call(); - } - else if (lex< exactly<'+'> >()) { - Unary_Expression* ex = SASS_MEMORY_NEW(Unary_Expression, pstate, Unary_Expression::PLUS, parse_factor()); - if (ex && ex->operand()) ex->is_delayed(ex->operand()->is_delayed()); - return ex; - } - else if (lex< exactly<'-'> >()) { - Unary_Expression* ex = SASS_MEMORY_NEW(Unary_Expression, pstate, Unary_Expression::MINUS, parse_factor()); - if (ex && ex->operand()) ex->is_delayed(ex->operand()->is_delayed()); - return ex; - } - else if (lex< exactly<'/'> >()) { - Unary_Expression* ex = SASS_MEMORY_NEW(Unary_Expression, pstate, Unary_Expression::SLASH, parse_factor()); - if (ex && ex->operand()) ex->is_delayed(ex->operand()->is_delayed()); - return ex; - } - else if (lex< sequence< kwd_not > >()) { - Unary_Expression* ex = SASS_MEMORY_NEW(Unary_Expression, pstate, Unary_Expression::NOT, parse_factor()); - if (ex && ex->operand()) ex->is_delayed(ex->operand()->is_delayed()); - return ex; - } - else { - return parse_value(); + Offset start(scanner.offset); + for (uint8_t i = 0; text[i]; i++) { + if (scanIdentChar(text[i], sensitive)) continue; + error("Expected " + name + ".", + scanner.rawSpanFrom(start)); } + if (!lookingAtIdentifierBody()) return; + error("Expected " + name + ".", + scanner.rawSpanFrom(start)); } - - bool number_has_zero(const sass::string& parsed) - { - size_t L = parsed.length(); - return !( (L > 0 && parsed.substr(0, 1) == ".") || - (L > 1 && parsed.substr(0, 2) == "0.") || - (L > 1 && parsed.substr(0, 2) == "-.") || - (L > 2 && parsed.substr(0, 3) == "-0.") ); - } - - Number* Parser::lexed_number(const SourceSpan& pstate, const sass::string& parsed) - { - Number* nr = SASS_MEMORY_NEW(Number, - pstate, - sass_strtod(parsed.c_str()), - "", - number_has_zero(parsed)); - nr->is_interpolant(false); - nr->is_delayed(true); - return nr; - } - - Number* Parser::lexed_percentage(const SourceSpan& pstate, const sass::string& parsed) - { - Number* nr = SASS_MEMORY_NEW(Number, - pstate, - sass_strtod(parsed.c_str()), - "%", - true); - nr->is_interpolant(false); - nr->is_delayed(true); - return nr; - } - - Number* Parser::lexed_dimension(const SourceSpan& pstate, const sass::string& parsed) - { - size_t L = parsed.length(); - size_t num_pos = parsed.find_first_not_of(" \n\r\t"); - if (num_pos == sass::string::npos) num_pos = L; - size_t unit_pos = parsed.find_first_not_of("-+0123456789.", num_pos); - if (parsed[unit_pos] == 'e' && is_number(parsed[unit_pos+1]) ) { - unit_pos = parsed.find_first_not_of("-+0123456789.", ++ unit_pos); - } - if (unit_pos == sass::string::npos) unit_pos = L; - const sass::string& num = parsed.substr(num_pos, unit_pos - num_pos); - Number* nr = SASS_MEMORY_NEW(Number, - pstate, - sass_strtod(num.c_str()), - Token(number(parsed.c_str())), - number_has_zero(parsed)); - nr->is_interpolant(false); - nr->is_delayed(true); - return nr; - } - - Value* Parser::lexed_hex_color(const SourceSpan& pstate, const sass::string& parsed) - { - Color_RGBA* color = NULL; - if (parsed[0] != '#') { - return SASS_MEMORY_NEW(String_Quoted, pstate, parsed); - } - // chop off the '#' - sass::string hext(parsed.substr(1)); - if (parsed.length() == 4) { - sass::string r(2, parsed[1]); - sass::string g(2, parsed[2]); - sass::string b(2, parsed[3]); - color = SASS_MEMORY_NEW(Color_RGBA, - pstate, - static_cast(strtol(r.c_str(), NULL, 16)), - static_cast(strtol(g.c_str(), NULL, 16)), - static_cast(strtol(b.c_str(), NULL, 16)), - 1, // alpha channel - parsed); - } - else if (parsed.length() == 5) { - sass::string r(2, parsed[1]); - sass::string g(2, parsed[2]); - sass::string b(2, parsed[3]); - sass::string a(2, parsed[4]); - color = SASS_MEMORY_NEW(Color_RGBA, - pstate, - static_cast(strtol(r.c_str(), NULL, 16)), - static_cast(strtol(g.c_str(), NULL, 16)), - static_cast(strtol(b.c_str(), NULL, 16)), - static_cast(strtol(a.c_str(), NULL, 16)) / 255, - parsed); - } - else if (parsed.length() == 7) { - sass::string r(parsed.substr(1,2)); - sass::string g(parsed.substr(3,2)); - sass::string b(parsed.substr(5,2)); - color = SASS_MEMORY_NEW(Color_RGBA, - pstate, - static_cast(strtol(r.c_str(), NULL, 16)), - static_cast(strtol(g.c_str(), NULL, 16)), - static_cast(strtol(b.c_str(), NULL, 16)), - 1, // alpha channel - parsed); - } - else if (parsed.length() == 9) { - sass::string r(parsed.substr(1,2)); - sass::string g(parsed.substr(3,2)); - sass::string b(parsed.substr(5,2)); - sass::string a(parsed.substr(7,2)); - color = SASS_MEMORY_NEW(Color_RGBA, - pstate, - static_cast(strtol(r.c_str(), NULL, 16)), - static_cast(strtol(g.c_str(), NULL, 16)), - static_cast(strtol(b.c_str(), NULL, 16)), - static_cast(strtol(a.c_str(), NULL, 16)) / 255, - parsed); - } - color->is_interpolant(false); - color->is_delayed(false); - return color; - } - - Value* Parser::color_or_string(const sass::string& lexed) const - { - if (auto color = name_to_color(lexed)) { - auto c = SASS_MEMORY_NEW(Color_RGBA, color); - c->is_delayed(true); - c->pstate(pstate); - c->disp(lexed); - return c; - } else { - return SASS_MEMORY_NEW(String_Constant, pstate, lexed); - } - } - - // parse one value for a list - ExpressionObj Parser::parse_value() - { - lex< css_comments >(false); - if (lex< ampersand >()) - { - if (match< ampersand >()) { - warning("In Sass, \"&&\" means two copies of the parent selector. You probably want to use \"and\" instead.", pstate); - } - return SASS_MEMORY_NEW(Parent_Reference, pstate); } - - if (lex< kwd_important >()) - { return SASS_MEMORY_NEW(String_Constant, pstate, "!important"); } - - // parse `10%4px` into separated items and not a schema - if (lex< sequence < percentage, lookahead < number > > >()) - { return lexed_percentage(lexed); } - - if (lex< sequence < number, lookahead< sequence < op, number > > > >()) - { return lexed_number(lexed); } - - // string may be interpolated - if (lex< sequence < quoted_string, lookahead < exactly <'-'> > > >()) - { return parse_string(); } - - if (const char* stop = peek< value_schema >()) - { return parse_value_schema(stop); } - - // string may be interpolated - if (lex< quoted_string >()) - { return parse_string(); } - - if (lex< kwd_true >()) - { return SASS_MEMORY_NEW(Boolean, pstate, true); } - - if (lex< kwd_false >()) - { return SASS_MEMORY_NEW(Boolean, pstate, false); } - - if (lex< kwd_null >()) - { return SASS_MEMORY_NEW(Null, pstate); } - - if (lex< identifier >()) { - return color_or_string(lexed); - } - - if (lex< percentage >()) - { return lexed_percentage(lexed); } - - // match hex number first because 0x000 looks like a number followed by an identifier - if (lex< sequence < alternatives< hex, hex0 >, negate < exactly<'-'> > > >()) - { return lexed_hex_color(lexed); } - - if (lex< hexa >()) - { return lexed_hex_color(lexed); } - - if (lex< sequence < exactly <'#'>, identifier > >()) - { return SASS_MEMORY_NEW(String_Quoted, pstate, lexed); } - - // also handle the 10em- foo special case - // alternatives < exactly < '.' >, .. > -- `1.5em-.75em` is split into a list, not a binary expression - if (lex< sequence< dimension, optional< sequence< exactly<'-'>, lookahead< alternatives < space > > > > > >()) - { return lexed_dimension(lexed); } - - if (lex< sequence< static_component, one_plus< strict_identifier > > >()) - { return SASS_MEMORY_NEW(String_Constant, pstate, lexed); } - - if (lex< number >()) - { return lexed_number(lexed); } - - if (lex< variable >()) - { return SASS_MEMORY_NEW(Variable, pstate, Util::normalize_underscores(lexed)); } - - css_error("Invalid CSS", " after ", ": expected expression (e.g. 1px, bold), was "); - - // unreachable statement - return {}; - } - - // this parses interpolation inside other strings - // means the result should later be quoted again - String_Obj Parser::parse_interpolated_chunk(Token chunk, bool constant, bool css) - { - const char* i = chunk.begin; - // see if there any interpolants - const char* p = constant ? find_first_in_interval< exactly >(i, chunk.end) : - find_first_in_interval< exactly, block_comment >(i, chunk.end); - - if (!p) { - String_Quoted* str_quoted = SASS_MEMORY_NEW(String_Quoted, pstate, sass::string(i, chunk.end), 0, false, false, true, css); - if (!constant && str_quoted->quote_mark()) str_quoted->quote_mark('*'); - return str_quoted; - } - - String_Schema_Obj schema = SASS_MEMORY_NEW(String_Schema, pstate, 0, css); - schema->is_interpolant(true); - while (i < chunk.end) { - p = constant ? find_first_in_interval< exactly >(i, chunk.end) : - find_first_in_interval< exactly, block_comment >(i, chunk.end); - if (p) { - if (i < p) { - // accumulate the preceding segment if it's nonempty - schema->append(SASS_MEMORY_NEW(String_Constant, pstate, sass::string(i, p), css)); - } - // we need to skip anything inside strings - // create a new target in parser/prelexer - if (peek < sequence < optional_spaces, exactly > >(p+2)) { position = p+2; - css_error("Invalid CSS", " after ", ": expected expression (e.g. 1px, bold), was "); - } - const char* j = skip_over_scopes< exactly, exactly >(p + 2, chunk.end); // find the closing brace - if (j) { --j; - // parse the interpolant and accumulate it - LocalOption partEnd(end, j); - LocalOption partBeg(position, p + 2); - ExpressionObj interp_node = parse_list(); - interp_node->is_interpolant(true); - schema->append(interp_node); - i = j; - } - else { - // throw an error if the interpolant is unterminated - error("unterminated interpolant inside string constant " + chunk.to_string()); - } - } - else { // no interpolants left; add the last segment if nonempty - // check if we need quotes here (was not sure after merge) - if (i < chunk.end) schema->append(SASS_MEMORY_NEW(String_Constant, pstate, sass::string(i, chunk.end), css)); - break; - } - ++ i; - } - - return schema.detach(); - } - - String_Schema_Obj Parser::parse_css_variable_value() - { - String_Schema_Obj schema = SASS_MEMORY_NEW(String_Schema, pstate); - sass::vector brackets; - while (true) { - if ( - (brackets.empty() && lex< css_variable_top_level_value >(false)) || - (!brackets.empty() && lex< css_variable_value >(false)) - ) { - Token str(lexed); - schema->append(SASS_MEMORY_NEW(String_Constant, pstate, str)); - } else if (ExpressionObj tok = lex_interpolation()) { - if (String_Schema* s = Cast(tok)) { - if (s->empty()) break; - schema->concat(s); - } else { - schema->append(tok); - } - } else if (lex< quoted_string >()) { - ExpressionObj tok = parse_string(); - if (tok.isNull()) break; - if (String_Schema* s = Cast(tok)) { - if (s->empty()) break; - schema->concat(s); - } else { - schema->append(tok); - } - } else if (lex< alternatives< exactly<'('>, exactly<'['>, exactly<'{'> > >()) { - const char opening_bracket = *(position - 1); - brackets.push_back(opening_bracket); - schema->append(SASS_MEMORY_NEW(String_Constant, pstate, sass::string(1, opening_bracket))); - } else if (const char *match = peek< alternatives< exactly<')'>, exactly<']'>, exactly<'}'> > >()) { - if (brackets.empty()) break; - const char closing_bracket = *(match - 1); - if (brackets.back() != Util::opening_bracket_for(closing_bracket)) { - sass::string message = ": expected \""; - message += Util::closing_bracket_for(brackets.back()); - message += "\", was "; - css_error("Invalid CSS", " after ", message); - } - lex< alternatives< exactly<')'>, exactly<']'>, exactly<'}'> > >(); - schema->append(SASS_MEMORY_NEW(String_Constant, pstate, sass::string(1, closing_bracket))); - brackets.pop_back(); - } else { - break; - } - } - - if (!brackets.empty()) { - sass::string message = ": expected \""; - message += Util::closing_bracket_for(brackets.back()); - message += "\", was "; - css_error("Invalid CSS", " after ", message); - } - - if (schema->empty()) error("Custom property values may not be empty."); - return schema.detach(); - } - - ValueObj Parser::parse_static_value() - { - lex< static_value >(); - Token str(lexed); - // static values always have trailing white- - // space and end delimiter (\s*[;]$) included - --pstate.offset.column; - --after_token.column; - --str.end; - --position; - - return color_or_string(str.time_wspace());; - } - - String_Obj Parser::parse_string() - { - return parse_interpolated_chunk(Token(lexed)); - } - - String_Obj Parser::parse_ie_property() - { - lex< ie_property >(); - Token str(lexed); - const char* i = str.begin; - // see if there any interpolants - const char* p = find_first_in_interval< exactly, block_comment >(str.begin, str.end); - if (!p) { - return SASS_MEMORY_NEW(String_Quoted, pstate, sass::string(str.begin, str.end)); - } - - String_Schema* schema = SASS_MEMORY_NEW(String_Schema, pstate); - while (i < str.end) { - p = find_first_in_interval< exactly, block_comment >(i, str.end); - if (p) { - if (i < p) { - schema->append(SASS_MEMORY_NEW(String_Constant, pstate, sass::string(i, p))); // accumulate the preceding segment if it's nonempty - } - if (peek < sequence < optional_spaces, exactly > >(p+2)) { position = p+2; - css_error("Invalid CSS", " after ", ": expected expression (e.g. 1px, bold), was "); - } - const char* j = skip_over_scopes< exactly, exactly >(p+2, str.end); // find the closing brace - if (j) { - // parse the interpolant and accumulate it - LocalOption partEnd(end, j); - LocalOption partBeg(position, p + 2); - ExpressionObj interp_node = parse_list(); - interp_node->is_interpolant(true); - schema->append(interp_node); - i = j; - } - else { - // throw an error if the interpolant is unterminated - error("unterminated interpolant inside IE function " + str.to_string()); - } - } - else { // no interpolants left; add the last segment if nonempty - if (i < str.end) { - schema->append(SASS_MEMORY_NEW(String_Constant, pstate, sass::string(i, str.end))); - } - break; - } - } - return schema; - } - - String_Obj Parser::parse_ie_keyword_arg() - { - String_Schema_Obj kwd_arg = SASS_MEMORY_NEW(String_Schema, pstate, 3); - if (lex< variable >()) { - kwd_arg->append(SASS_MEMORY_NEW(Variable, pstate, Util::normalize_underscores(lexed))); - } else { - lex< alternatives< identifier_schema, identifier > >(); - kwd_arg->append(SASS_MEMORY_NEW(String_Constant, pstate, lexed)); - } - lex< exactly<'='> >(); - kwd_arg->append(SASS_MEMORY_NEW(String_Constant, pstate, lexed)); - if (peek< variable >()) kwd_arg->append(parse_list()); - else if (lex< number >()) { - sass::string parsed(lexed); - Util::normalize_decimals(parsed); - kwd_arg->append(lexed_number(parsed)); - } - else if (peek < ie_keyword_arg_value >()) { kwd_arg->append(parse_list()); } - return kwd_arg; - } - - String_Schema_Obj Parser::parse_value_schema(const char* stop) - { - // initialize the string schema object to add tokens - String_Schema_Obj schema = SASS_MEMORY_NEW(String_Schema, pstate); - - if (peek>()) { - css_error("Invalid CSS", " after ", ": expected expression (e.g. 1px, bold), was "); - } - - const char* e; - const char* ee = end; - end = stop; - size_t num_items = 0; - bool need_space = false; - while (position < stop) { - // parse space between tokens - if (lex< spaces >() && num_items) { - need_space = true; - } - if (need_space) { - need_space = false; - // schema->append(SASS_MEMORY_NEW(String_Constant, pstate, " ")); - } - if ((e = peek< re_functional >()) && e < stop) { - schema->append(parse_function_call()); - } - // lex an interpolant /#{...}/ - else if (lex< exactly < hash_lbrace > >()) { - // Try to lex static expression first - if (peek< exactly< rbrace > >()) { - css_error("Invalid CSS", " after ", ": expected expression (e.g. 1px, bold), was "); - } - ExpressionObj ex; - if (lex< re_static_expression >()) { - ex = SASS_MEMORY_NEW(String_Constant, pstate, lexed); - } else { - ex = parse_list(true); - } - ex->is_interpolant(true); - schema->append(ex); - if (!lex < exactly < rbrace > >()) { - css_error("Invalid CSS", " after ", ": expected \"}\", was "); - } - } - // lex some string constants or other valid token - // Note: [-+] chars are left over from i.e. `#{3}+3` - else if (lex< alternatives < exactly<'%'>, exactly < '-' >, exactly < '+' > > >()) { - schema->append(SASS_MEMORY_NEW(String_Constant, pstate, lexed)); - } - // lex a quoted string - else if (lex< quoted_string >()) { - // need_space = true; - // if (schema->length()) schema->append(SASS_MEMORY_NEW(String_Constant, pstate, " ")); - // else need_space = true; - schema->append(parse_string()); - if ((*position == '"' || *position == '\'') || peek < alternatives < alpha > >()) { - // need_space = true; - } - if (peek < exactly < '-' > >()) break; - } - else if (lex< identifier >()) { - schema->append(SASS_MEMORY_NEW(String_Constant, pstate, lexed)); - if ((*position == '"' || *position == '\'') || peek < alternatives < alpha > >()) { - // need_space = true; - } - } - // lex (normalized) variable - else if (lex< variable >()) { - sass::string name(Util::normalize_underscores(lexed)); - schema->append(SASS_MEMORY_NEW(Variable, pstate, name)); - } - // lex percentage value - else if (lex< percentage >()) { - schema->append(lexed_percentage(lexed)); - } - // lex dimension value - else if (lex< dimension >()) { - schema->append(lexed_dimension(lexed)); - } - // lex number value - else if (lex< number >()) { - schema->append(lexed_number(lexed)); - } - // lex hex color value - else if (lex< sequence < hex, negate < exactly < '-' > > > >()) { - schema->append(lexed_hex_color(lexed)); - } - else if (lex< sequence < exactly <'#'>, identifier > >()) { - schema->append(SASS_MEMORY_NEW(String_Quoted, pstate, lexed)); - } - // lex a value in parentheses - else if (peek< parenthese_scope >()) { - schema->append(parse_factor()); - } - else { - break; - } - ++num_items; - } - if (position != stop) { - schema->append(SASS_MEMORY_NEW(String_Constant, pstate, sass::string(position, stop))); - position = stop; - } - end = ee; - return schema; - } - - // this parses interpolation outside other strings - // means the result must not be quoted again later - String_Obj Parser::parse_identifier_schema() - { - Token id(lexed); - const char* i = id.begin; - // see if there any interpolants - const char* p = find_first_in_interval< exactly, block_comment >(id.begin, id.end); - if (!p) { - return SASS_MEMORY_NEW(String_Constant, pstate, sass::string(id.begin, id.end)); - } - - String_Schema_Obj schema = SASS_MEMORY_NEW(String_Schema, pstate); - while (i < id.end) { - p = find_first_in_interval< exactly, block_comment >(i, id.end); - if (p) { - if (i < p) { - // accumulate the preceding segment if it's nonempty - const char* o = position; position = i; - schema->append(parse_value_schema(p)); - position = o; - } - // we need to skip anything inside strings - // create a new target in parser/prelexer - if (peek < sequence < optional_spaces, exactly > >(p+2)) { position = p; - css_error("Invalid CSS", " after ", ": expected expression (e.g. 1px, bold), was "); - } - const char* j = skip_over_scopes< exactly, exactly >(p+2, id.end); // find the closing brace - if (j) { - // parse the interpolant and accumulate it - LocalOption partEnd(end, j); - LocalOption partBeg(position, p + 2); - ExpressionObj interp_node = parse_list(DELAYED); - interp_node->is_interpolant(true); - schema->append(interp_node); - // schema->has_interpolants(true); - i = j; - } - else { - // throw an error if the interpolant is unterminated - error("unterminated interpolant inside interpolated identifier " + id.to_string()); - } - } - else { // no interpolants left; add the last segment if nonempty - if (i < end) { - const char* o = position; position = i; - schema->append(parse_value_schema(id.end)); - position = o; - } - break; - } - } - return schema ? schema.detach() : 0; - } - - // calc functions should preserve arguments - Function_Call_Obj Parser::parse_calc_function() - { - lex< identifier >(); - sass::string name(lexed); - SourceSpan call_pos = pstate; - lex< exactly<'('> >(); - SourceSpan arg_pos = pstate; - const char* arg_beg = position; - parse_list(); - const char* arg_end = position; - lex< skip_over_scopes < - exactly < '(' >, - exactly < ')' > - > >(); - - Argument_Obj arg = SASS_MEMORY_NEW(Argument, arg_pos, parse_interpolated_chunk(Token(arg_beg, arg_end))); - Arguments_Obj args = SASS_MEMORY_NEW(Arguments, arg_pos); - args->append(arg); - return SASS_MEMORY_NEW(Function_Call, call_pos, name, args); - } - - String_Obj Parser::parse_url_function_string() - { - sass::string prefix(""); - if (lex< uri_prefix >()) { - prefix = sass::string(lexed); - } - - lex < optional_spaces >(); - String_Obj url_string = parse_url_function_argument(); - - sass::string suffix(""); - if (lex< real_uri_suffix >()) { - suffix = sass::string(lexed); - } - - sass::string uri(""); - if (url_string) { - uri = url_string->to_string({ NESTED, 5 }); - } - - if (String_Schema* schema = Cast(url_string)) { - String_Schema_Obj res = SASS_MEMORY_NEW(String_Schema, pstate); - res->append(SASS_MEMORY_NEW(String_Constant, pstate, prefix)); - res->append(schema); - res->append(SASS_MEMORY_NEW(String_Constant, pstate, suffix)); - return res; - } else { - sass::string res = prefix + uri + suffix; - return SASS_MEMORY_NEW(String_Constant, pstate, res); - } - } - - String_Obj Parser::parse_url_function_argument() - { - const char* p = position; - - sass::string uri(""); - if (lex< real_uri_value >(false)) { - uri = lexed.to_string(); - } - - if (peek< exactly< hash_lbrace > >()) { - const char* pp = position; - // TODO: error checking for unclosed interpolants - while (pp && peek< exactly< hash_lbrace > >(pp)) { - pp = sequence< interpolant, real_uri_value >(pp); - } - if (!pp) return {}; - position = pp; - return parse_interpolated_chunk(Token(p, position)); - } - else if (uri != "") { - sass::string res = Util::rtrim(uri); - return SASS_MEMORY_NEW(String_Constant, pstate, res); - } - - return {}; - } - - Function_Call_Obj Parser::parse_function_call() - { - lex< identifier >(); - sass::string name(lexed); - - if (Util::normalize_underscores(name) == "content-exists" && stack.back() != Scope::Mixin) - { error("Cannot call content-exists() except within a mixin."); } - - SourceSpan call_pos = pstate; - Arguments_Obj args = parse_arguments(); - return SASS_MEMORY_NEW(Function_Call, call_pos, name, args); - } - - Function_Call_Obj Parser::parse_function_call_schema() - { - String_Obj name = parse_identifier_schema(); - SourceSpan source_position_of_call = pstate; - Arguments_Obj args = parse_arguments(); - - return SASS_MEMORY_NEW(Function_Call, source_position_of_call, name, args); - } - - Content_Obj Parser::parse_content_directive() - { - SourceSpan call_pos = pstate; - Arguments_Obj args = parse_arguments(); - - return SASS_MEMORY_NEW(Content, call_pos, args); - } - - If_Obj Parser::parse_if_directive(bool else_if) - { - stack.push_back(Scope::Control); - SourceSpan if_source_position = pstate; - bool root = block_stack.back()->is_root(); - ExpressionObj predicate = parse_list(); - Block_Obj block = parse_block(root); - Block_Obj alternative; - - // only throw away comment if we parse a case - // we want all other comments to be parsed - if (lex_css< elseif_directive >()) { - alternative = SASS_MEMORY_NEW(Block, pstate); - alternative->append(parse_if_directive(true)); - } - else if (lex_css< kwd_else_directive >()) { - alternative = parse_block(root); - } - stack.pop_back(); - return SASS_MEMORY_NEW(If, if_source_position, predicate, block, alternative); - } - - ForRuleObj Parser::parse_for_directive() - { - stack.push_back(Scope::Control); - SourceSpan for_source_position = pstate; - bool root = block_stack.back()->is_root(); - lex_variable(); - sass::string var(Util::normalize_underscores(lexed)); - if (!lex< kwd_from >()) error("expected 'from' keyword in @for directive"); - ExpressionObj lower_bound = parse_expression(); - bool inclusive = false; - if (lex< kwd_through >()) inclusive = true; - else if (lex< kwd_to >()) inclusive = false; - else error("expected 'through' or 'to' keyword in @for directive"); - ExpressionObj upper_bound = parse_expression(); - Block_Obj body = parse_block(root); - stack.pop_back(); - return SASS_MEMORY_NEW(ForRule, for_source_position, var, lower_bound, upper_bound, body, inclusive); - } - - // helper to parse a var token - Token Parser::lex_variable() + // EO expectIdentifier + + // Throws a parser error associated with [pstate]. + void Parser::error(sass::string message, SourceSpan pstate) { - // peek for dollar sign first - if (!peek< exactly <'$'> >()) { - css_error("Invalid CSS", " after ", ": expected \"$\", was "); - } - // we expect a simple identifier as the call name - if (!lex< sequence < exactly <'$'>, identifier > >()) { - lex< exactly <'$'> >(); // move pstate and position up - css_error("Invalid CSS", " after ", ": expected identifier, was "); - } - // return object - return lexed; - } - // helper to parse identifier - Token Parser::lex_identifier() - { - // we expect a simple identifier as the call name - if (!lex< identifier >()) { // ToDo: pstate wrong? - css_error("Invalid CSS", " after ", ": expected identifier, was "); - } - // return object - return lexed; + callStackFrame frame(compiler, BackTrace(pstate)); + throw Exception::ParserException(compiler, message); } + // EO error - EachRuleObj Parser::parse_each_directive() - { - stack.push_back(Scope::Control); - SourceSpan each_source_position = pstate; - bool root = block_stack.back()->is_root(); - sass::vector vars; - lex_variable(); - vars.push_back(Util::normalize_underscores(lexed)); - while (lex< exactly<','> >()) { - if (!lex< variable >()) error("@each directive requires an iteration variable"); - vars.push_back(Util::normalize_underscores(lexed)); - } - if (!lex< kwd_in >()) error("expected 'in' keyword in @each directive"); - ExpressionObj list = parse_list(); - Block_Obj body = parse_block(root); - stack.pop_back(); - return SASS_MEMORY_NEW(EachRule, each_source_position, vars, list, body); - } - - // called after parsing `kwd_while_directive` - WhileRuleObj Parser::parse_while_directive() - { - stack.push_back(Scope::Control); - bool root = block_stack.back()->is_root(); - // create the initial while call object - WhileRuleObj call = SASS_MEMORY_NEW(WhileRule, pstate, ExpressionObj{}, Block_Obj{}); - // parse mandatory predicate - ExpressionObj predicate = parse_list(); - List_Obj l = Cast(predicate); - if (!predicate || (l && !l->length())) { - css_error("Invalid CSS", " after ", ": expected expression (e.g. 1px, bold), was ", false); - } - call->predicate(predicate); - // parse mandatory block - call->block(parse_block(root)); - // return ast node - stack.pop_back(); - // return ast node - return call.detach(); - } - - - sass::vector Parser::parseCssMediaQueries() - { - sass::vector result; - do { - if (auto query = parseCssMediaQuery()) { - result.push_back(query); - } - } while (lex>()); - return result; - } - - sass::string Parser::parseIdentifier() - { - if (lex < identifier >(false)) { - return sass::string(lexed); - } - return sass::string(); - } - - CssMediaQuery_Obj Parser::parseCssMediaQuery() - { - CssMediaQuery_Obj result = SASS_MEMORY_NEW(CssMediaQuery, pstate); - lex(false); - - // Check if any tokens are to parse - if (!peek_css>()) { - - sass::string token1(parseIdentifier()); - lex(false); - - if (token1.empty()) { - return {}; - } - - sass::string token2(parseIdentifier()); - lex(false); - - if (Util::equalsLiteral("and", token2)) { - result->type(token1); - } - else { - if (token2.empty()) { - result->type(token1); - } - else { - result->modifier(token1); - result->type(token2); - } - - if (lex < kwd_and >()) { - lex(false); - } - else { - return result; - } - - } - - } - - sass::vector queries; - - do { - lex(false); - - if (lex>()) { - // In dart sass parser returns a pure string - if (lex < skip_over_scopes < exactly < '(' >, exactly < ')' > > >()) { - sass::string decl("(" + sass::string(lexed)); - queries.push_back(decl); - } - // Should be: parseDeclarationValue; - if (!lex>()) { - // Should we throw an error here? - } - } - } while (lex < kwd_and >()); - - result->features(queries); - - if (result->features().empty()) { - if (result->type().empty()) { - return {}; - } - } - - return result; - } - - - // EO parse_while_directive - MediaRule_Obj Parser::parseMediaRule() - { - MediaRule_Obj rule = SASS_MEMORY_NEW(MediaRule, pstate); - stack.push_back(Scope::Media); - rule->schema(parse_media_queries()); - parse_block_comments(false); - rule->block(parse_css_block()); - stack.pop_back(); - return rule; - } - - List_Obj Parser::parse_media_queries() - { - advanceToNextToken(); - List_Obj queries = SASS_MEMORY_NEW(List, pstate, 0, SASS_COMMA); - if (!peek_css < exactly <'{'> >()) queries->append(parse_media_query()); - while (lex_css < exactly <','> >()) queries->append(parse_media_query()); - queries->update_pstate(pstate); - return queries.detach(); - } - - // Expression* Parser::parse_media_query() - Media_Query_Obj Parser::parse_media_query() - { - advanceToNextToken(); - Media_Query_Obj media_query = SASS_MEMORY_NEW(Media_Query, pstate); - if (lex < kwd_not >()) { media_query->is_negated(true); lex < css_comments >(false); } - else if (lex < kwd_only >()) { media_query->is_restricted(true); lex < css_comments >(false); } - - if (lex < identifier_schema >()) media_query->media_type(parse_identifier_schema()); - else if (lex < identifier >()) media_query->media_type(parse_interpolated_chunk(lexed)); - else media_query->append(parse_media_expression()); - - while (lex_css < kwd_and >()) media_query->append(parse_media_expression()); - if (lex < identifier_schema >()) { - String_Schema_Obj schema = SASS_MEMORY_NEW(String_Schema, pstate); - if (media_query->media_type()) { - schema->append(media_query->media_type()); - schema->append(SASS_MEMORY_NEW(String_Constant, pstate, " ")); - } - schema->append(parse_identifier_schema()); - media_query->media_type(schema); - } - while (lex_css < kwd_and >()) media_query->append(parse_media_expression()); - - media_query->update_pstate(pstate); - - return media_query; - } - - Media_Query_ExpressionObj Parser::parse_media_expression() - { - if (lex < identifier_schema >()) { - String_Obj ss = parse_identifier_schema(); - return SASS_MEMORY_NEW(Media_Query_Expression, pstate, ss, ExpressionObj{}, true); - } - if (!lex_css< exactly<'('> >()) { - error("media query expression must begin with '('"); - } - ExpressionObj feature; - if (peek_css< exactly<')'> >()) { - error("media feature required in media query expression"); - } - feature = parse_expression(); - ExpressionObj expression; - if (lex_css< exactly<':'> >()) { - expression = parse_list(DELAYED); - } - if (!lex_css< exactly<')'> >()) { - error("unclosed parenthesis in media query expression"); - } - return SASS_MEMORY_NEW(Media_Query_Expression, feature->pstate(), feature, expression); - } - - // lexed after `kwd_supports_directive` - // these are very similar to media blocks - SupportsRuleObj Parser::parse_supports_directive() - { - SupportsConditionObj cond = parse_supports_condition(/*top_level=*/true); - // create the ast node object for the support queries - SupportsRuleObj query = SASS_MEMORY_NEW(SupportsRule, pstate, cond); - // additional block is mandatory - // parse inner block - query->block(parse_block()); - // return ast node - return query; - } - - // parse one query operation - // may encounter nested queries - SupportsConditionObj Parser::parse_supports_condition(bool top_level) - { - lex < css_whitespace >(); - SupportsConditionObj cond; - if ((cond = parse_supports_negation())) return cond; - if ((cond = parse_supports_operator(top_level))) return cond; - if ((cond = parse_supports_interpolation())) return cond; - return cond; - } - - SupportsConditionObj Parser::parse_supports_negation() - { - if (!lex < kwd_not >()) return {}; - SupportsConditionObj cond = parse_supports_condition_in_parens(/*parens_required=*/true); - return SASS_MEMORY_NEW(SupportsNegation, pstate, cond); - } - - SupportsConditionObj Parser::parse_supports_operator(bool top_level) - { - SupportsConditionObj cond = parse_supports_condition_in_parens(/*parens_required=*/top_level); - if (cond.isNull()) return {}; - - while (true) { - SupportsOperation::Operand op = SupportsOperation::OR; - if (lex < kwd_and >()) { op = SupportsOperation::AND; } - else if(!lex < kwd_or >()) { break; } - - lex < css_whitespace >(); - SupportsConditionObj right = parse_supports_condition_in_parens(/*parens_required=*/true); - - // SupportsCondition* cc = SASS_MEMORY_NEW(SupportsCondition, *static_cast(cond)); - cond = SASS_MEMORY_NEW(SupportsOperation, pstate, cond, right, op); - } - return cond; - } - - SupportsConditionObj Parser::parse_supports_interpolation() - { - if (!lex < interpolant >()) return {}; - - String_Obj interp = parse_interpolated_chunk(lexed); - if (!interp) return {}; - - return SASS_MEMORY_NEW(Supports_Interpolation, pstate, interp); - } - - // TODO: This needs some major work. Although feature conditions - // look like declarations their semantics differ significantly - SupportsConditionObj Parser::parse_supports_declaration() - { - SupportsCondition* cond; - // parse something declaration like - ExpressionObj feature = parse_expression(); - ExpressionObj expression; - if (lex_css< exactly<':'> >()) { - expression = parse_list(DELAYED); - } - if (!feature || !expression) error("@supports condition expected declaration"); - cond = SASS_MEMORY_NEW(SupportsDeclaration, - feature->pstate(), - feature, - expression); - // ToDo: maybe we need an additional error condition? - return cond; - } - - SupportsConditionObj Parser::parse_supports_condition_in_parens(bool parens_required) - { - SupportsConditionObj interp = parse_supports_interpolation(); - if (interp != nullptr) return interp; - - if (!lex < exactly <'('> >()) { - if (parens_required) { - css_error("Invalid CSS", " after ", ": expected @supports condition (e.g. (display: flexbox)), was ", /*trim=*/false); - } else { - return {}; - } - } - lex < css_whitespace >(); - - SupportsConditionObj cond = parse_supports_condition(/*top_level=*/false); - if (cond.isNull()) cond = parse_supports_declaration(); - if (!lex < exactly <')'> >()) error("unclosed parenthesis in @supports declaration"); - - lex < css_whitespace >(); - return cond; - } - - AtRootRuleObj Parser::parse_at_root_block() - { - stack.push_back(Scope::AtRoot); - SourceSpan at_source_position = pstate; - Block_Obj body; - At_Root_Query_Obj expr; - Lookahead lookahead_result; - if (lex_css< exactly<'('> >()) { - expr = parse_at_root_query(); - } - if (peek_css < exactly<'{'> >()) { - lex (); - body = parse_block(true); - } - else if ((lookahead_result = lookahead_for_selector(position)).found) { - StyleRuleObj r = parse_ruleset(lookahead_result); - body = SASS_MEMORY_NEW(Block, r->pstate(), 1, true); - body->append(r); - } - AtRootRuleObj at_root = SASS_MEMORY_NEW(AtRootRule, at_source_position, body); - if (!expr.isNull()) at_root->expression(expr); - stack.pop_back(); - return at_root; - } - - At_Root_Query_Obj Parser::parse_at_root_query() - { - if (peek< exactly<')'> >()) error("at-root feature required in at-root expression"); - - if (!peek< alternatives< kwd_with_directive, kwd_without_directive > >()) { - css_error("Invalid CSS", " after ", ": expected \"with\" or \"without\", was "); - } - - ExpressionObj feature = parse_list(); - if (!lex_css< exactly<':'> >()) error("style declaration must contain a value"); - ExpressionObj expression = parse_list(); - List_Obj value = SASS_MEMORY_NEW(List, feature->pstate(), 1); - - if (expression->concrete_type() == Expression::LIST) { - value = Cast(expression); - } - else value->append(expression); - - At_Root_Query_Obj cond = SASS_MEMORY_NEW(At_Root_Query, - value->pstate(), - feature, - value); - if (!lex_css< exactly<')'> >()) error("unclosed parenthesis in @at-root expression"); - return cond; - } - - AtRuleObj Parser::parse_directive() - { - AtRuleObj directive = SASS_MEMORY_NEW(AtRule, pstate, lexed); - String_Schema_Obj val = parse_almost_any_value(); - // strip left and right if they are of type string - directive->value(val); - if (peek< exactly<'{'> >()) { - directive->block(parse_block()); - } - return directive; - } - - ExpressionObj Parser::lex_interpolation() - { - if (lex < interpolant >(true) != NULL) { - return parse_interpolated_chunk(lexed, true); - } - return {}; - } - - ExpressionObj Parser::lex_interp_uri() - { - // create a string schema by lexing optional interpolations - return lex_interp< re_string_uri_open, re_string_uri_close >(); - } - - ExpressionObj Parser::lex_interp_string() - { - ExpressionObj rv; - if ((rv = lex_interp< re_string_double_open, re_string_double_close >())) return rv; - if ((rv = lex_interp< re_string_single_open, re_string_single_close >())) return rv; - return rv; - } - - ExpressionObj Parser::lex_almost_any_value_chars() - { - const char* match = - lex < - one_plus < - alternatives < - exactly <'>'>, - sequence < - exactly <'\\'>, - any_char - >, - sequence < - negate < - sequence < - exactly < url_kwd >, - exactly <'('> - > - >, - neg_class_char < - almost_any_value_class - > - >, - sequence < - exactly <'/'>, - negate < - alternatives < - exactly <'/'>, - exactly <'*'> - > - > - >, - sequence < - exactly <'\\'>, - exactly <'#'>, - negate < - exactly <'{'> - > - >, - sequence < - exactly <'!'>, - negate < - alpha - > - > - > - > - >(false); - if (match) { - return SASS_MEMORY_NEW(String_Constant, pstate, lexed); - } - return {}; - } - - ExpressionObj Parser::lex_almost_any_value_token() - { - ExpressionObj rv; - if (*position == 0) return {}; - if ((rv = lex_almost_any_value_chars())) return rv; - // if ((rv = lex_block_comment())) return rv; - // if ((rv = lex_single_line_comment())) return rv; - if ((rv = lex_interp_string())) return rv; - if ((rv = lex_interp_uri())) return rv; - if ((rv = lex_interpolation())) return rv; - if (lex< alternatives< hex, hex0 > >()) - { return lexed_hex_color(lexed); } - return rv; - } - - String_Schema_Obj Parser::parse_almost_any_value() - { - - String_Schema_Obj schema = SASS_MEMORY_NEW(String_Schema, pstate); - if (*position == 0) return {}; - lex < spaces >(false); - ExpressionObj token = lex_almost_any_value_token(); - if (!token) return {}; - schema->append(token); - if (*position == 0) { - schema->rtrim(); - return schema.detach(); - } - - while ((token = lex_almost_any_value_token())) { - schema->append(token); - } - - lex < css_whitespace >(); - - schema->rtrim(); - - return schema.detach(); - } - - WarningRuleObj Parser::parse_warning() - { - if (stack.back() != Scope::Root && - stack.back() != Scope::Function && - stack.back() != Scope::Mixin && - stack.back() != Scope::Control && - stack.back() != Scope::Rules) { - error("Illegal nesting: Only properties may be nested beneath properties."); - } - return SASS_MEMORY_NEW(WarningRule, pstate, parse_list(DELAYED)); - } - - ErrorRuleObj Parser::parse_error() - { - if (stack.back() != Scope::Root && - stack.back() != Scope::Function && - stack.back() != Scope::Mixin && - stack.back() != Scope::Control && - stack.back() != Scope::Rules) { - error("Illegal nesting: Only properties may be nested beneath properties."); - } - return SASS_MEMORY_NEW(ErrorRule, pstate, parse_list(DELAYED)); - } - - DebugRuleObj Parser::parse_debug() - { - if (stack.back() != Scope::Root && - stack.back() != Scope::Function && - stack.back() != Scope::Mixin && - stack.back() != Scope::Control && - stack.back() != Scope::Rules) { - error("Illegal nesting: Only properties may be nested beneath properties."); - } - return SASS_MEMORY_NEW(DebugRule, pstate, parse_list(DELAYED)); - } - - Return_Obj Parser::parse_return_directive() - { - // check that we do not have an empty list (ToDo: check if we got all cases) - if (peek_css < alternatives < exactly < ';' >, exactly < '}' >, end_of_file > >()) - { css_error("Invalid CSS", " after ", ": expected expression (e.g. 1px, bold), was "); } - return SASS_MEMORY_NEW(Return, pstate, parse_list()); - } - - Lookahead Parser::lookahead_for_selector(const char* start) - { - // init result struct - Lookahead rv = Lookahead(); - // get start position - const char* p = start ? start : position; - // match in one big "regex" - rv.error = p; - if (const char* q = - peek < - re_selector_list - >(p) - ) { - bool could_be_property = peek< sequence< exactly<'-'>, exactly<'-'> > >(p) != 0; - bool could_be_escaped = false; - while (p < q) { - // did we have interpolations? - if (*p == '#' && *(p+1) == '{') { - rv.has_interpolants = true; - p = q; break; - } - // A property that's ambiguous with a nested selector is interpreted as a - // custom property. - if (*p == ':' && !could_be_escaped) { - rv.is_custom_property = could_be_property || p+1 == q || peek< space >(p+1); - } - could_be_escaped = *p == '\\'; - ++ p; - } - // store anyway } - - - // ToDo: remove - rv.error = q; - rv.position = q; - // check expected opening bracket - // only after successful matching - if (peek < exactly<'{'> >(q)) rv.found = q; - // else if (peek < end_of_file >(q)) rv.found = q; - else if (peek < exactly<'('> >(q)) rv.found = q; - // else if (peek < exactly<';'> >(q)) rv.found = q; - // else if (peek < exactly<'}'> >(q)) rv.found = q; - if (rv.found || *p == 0) rv.error = 0; - } - - rv.parsable = ! rv.has_interpolants; - - // return result - return rv; - - } - // EO lookahead_for_selector - - // used in parse_block_nodes and parse_special_directive - // ToDo: actual usage is still not really clear to me? - Lookahead Parser::lookahead_for_include(const char* start) - { - // we actually just lookahead for a selector - Lookahead rv = lookahead_for_selector(start); - // but the "found" rules are different - if (const char* p = rv.position) { - // check for additional abort condition - if (peek < exactly<';'> >(p)) rv.found = p; - else if (peek < exactly<'}'> >(p)) rv.found = p; - } - // return result - return rv; - } - // EO lookahead_for_include - - // look ahead for a token with interpolation in it - // we mostly use the result if there is an interpolation - // everything that passes here gets parsed as one schema - // meaning it will not be parsed as a space separated list - Lookahead Parser::lookahead_for_value(const char* start) - { - // init result struct - Lookahead rv = Lookahead(); - // get start position - const char* p = start ? start : position; - // match in one big "regex" - if (const char* q = - peek < - non_greedy < - alternatives < - // consume whitespace - block_comment, // spaces, - // main tokens - sequence < - interpolant, - optional < - quoted_string - > - >, - identifier, - variable, - // issue #442 - sequence < - parenthese_scope, - interpolant, - optional < - quoted_string - > - > - >, - sequence < - // optional_spaces, - alternatives < - // end_of_file, - exactly<'{'>, - exactly<'}'>, - exactly<';'> - > - > - > - >(p) - ) { - if (p == q) return rv; - while (p < q) { - // did we have interpolations? - if (*p == '#' && *(p+1) == '{') { - rv.has_interpolants = true; - p = q; break; - } - ++ p; - } - // store anyway - // ToDo: remove - rv.position = q; - // check expected opening bracket - // only after successful matching - if (peek < exactly<'{'> >(q)) rv.found = q; - else if (peek < exactly<';'> >(q)) rv.found = q; - else if (peek < exactly<'}'> >(q)) rv.found = q; - } - - // return result - return rv; - } - // EO lookahead_for_value - - void Parser::read_bom() - { - size_t skip = 0; - sass::string encoding; - bool utf_8 = false; - switch ((unsigned char)position[0]) { - case 0xEF: - skip = check_bom_chars(position, end, utf_8_bom, 3); - encoding = "UTF-8"; - utf_8 = true; - break; - case 0xFE: - skip = check_bom_chars(position, end, utf_16_bom_be, 2); - encoding = "UTF-16 (big endian)"; - break; - case 0xFF: - skip = check_bom_chars(position, end, utf_16_bom_le, 2); - skip += (skip ? check_bom_chars(position, end, utf_32_bom_le, 4) : 0); - encoding = (skip == 2 ? "UTF-16 (little endian)" : "UTF-32 (little endian)"); - break; - case 0x00: - skip = check_bom_chars(position, end, utf_32_bom_be, 4); - encoding = "UTF-32 (big endian)"; - break; - case 0x2B: - skip = check_bom_chars(position, end, utf_7_bom_1, 4) - | check_bom_chars(position, end, utf_7_bom_2, 4) - | check_bom_chars(position, end, utf_7_bom_3, 4) - | check_bom_chars(position, end, utf_7_bom_4, 4) - | check_bom_chars(position, end, utf_7_bom_5, 5); - encoding = "UTF-7"; - break; - case 0xF7: - skip = check_bom_chars(position, end, utf_1_bom, 3); - encoding = "UTF-1"; - break; - case 0xDD: - skip = check_bom_chars(position, end, utf_ebcdic_bom, 4); - encoding = "UTF-EBCDIC"; - break; - case 0x0E: - skip = check_bom_chars(position, end, scsu_bom, 3); - encoding = "SCSU"; - break; - case 0xFB: - skip = check_bom_chars(position, end, bocu_1_bom, 3); - encoding = "BOCU-1"; - break; - case 0x84: - skip = check_bom_chars(position, end, gb_18030_bom, 4); - encoding = "GB-18030"; - break; - default: break; - } - if (skip > 0 && !utf_8) error("only UTF-8 documents are currently supported; your document appears to be " + encoding); - position += skip; - } - - size_t check_bom_chars(const char* src, const char *end, const unsigned char* bom, size_t len) - { - size_t skip = 0; - if (src + len > end) return 0; - for (size_t i = 0; i < len; ++i, ++skip) { - if ((unsigned char) src[i] != bom[i]) return 0; - } - return skip; - } - - - ExpressionObj Parser::fold_operands(ExpressionObj base, sass::vector& operands, Operand op) - { - for (size_t i = 0, S = operands.size(); i < S; ++i) { - base = SASS_MEMORY_NEW(Binary_Expression, base->pstate(), op, base, operands[i]); - } - return base; - } - - ExpressionObj Parser::fold_operands(ExpressionObj base, sass::vector& operands, sass::vector& ops, size_t i) - { - if (String_Schema* schema = Cast(base)) { - // return schema; - if (schema->has_interpolants()) { - if (i + 1 < operands.size() && ( - (ops[0].operand == Sass_OP::EQ) - || (ops[0].operand == Sass_OP::ADD) - || (ops[0].operand == Sass_OP::DIV) - || (ops[0].operand == Sass_OP::MUL) - || (ops[0].operand == Sass_OP::NEQ) - || (ops[0].operand == Sass_OP::LT) - || (ops[0].operand == Sass_OP::GT) - || (ops[0].operand == Sass_OP::LTE) - || (ops[0].operand == Sass_OP::GTE) - )) { - ExpressionObj rhs = fold_operands(operands[i], operands, ops, i + 1); - rhs = SASS_MEMORY_NEW(Binary_Expression, base->pstate(), ops[0], schema, rhs); - return rhs; - } - // return schema; - } - } - - if (operands.size() > Constants::MaxCallStack) { - // XXX: this is never hit via spec tests - sass::ostream stm; - stm << "Stack depth exceeded max of " << Constants::MaxCallStack; - error(stm.str()); - } - - for (size_t S = operands.size(); i < S; ++i) { - if (String_Schema* schema = Cast(operands[i])) { - if (schema->has_interpolants()) { - if (i + 1 < S) { - // this whole branch is never hit via spec tests - ExpressionObj rhs = fold_operands(operands[i+1], operands, ops, i + 2); - rhs = SASS_MEMORY_NEW(Binary_Expression, base->pstate(), ops[i], schema, rhs); - base = SASS_MEMORY_NEW(Binary_Expression, base->pstate(), ops[i], base, rhs); - return base; - } - base = SASS_MEMORY_NEW(Binary_Expression, base->pstate(), ops[i], base, operands[i]); - return base; - } else { - base = SASS_MEMORY_NEW(Binary_Expression, base->pstate(), ops[i], base, operands[i]); - } - } else { - base = SASS_MEMORY_NEW(Binary_Expression, base->pstate(), ops[i], base, operands[i]); - } - Binary_Expression* b = Cast(base.ptr()); - if (b && ops[i].operand == Sass_OP::DIV && b->left()->is_delayed() && b->right()->is_delayed()) { - base->is_delayed(true); - } - } - // nested binary expression are never to be delayed - if (Binary_Expression* b = Cast(base)) { - if (Cast(b->left())) base->set_delayed(false); - if (Cast(b->right())) base->set_delayed(false); - } - return base; - } - - void Parser::error(sass::string msg) - { - traces.push_back(Backtrace(pstate)); - throw Exception::InvalidSass(pstate, traces, msg); - } - - // print a css parsing error with actual context information from parsed source - void Parser::css_error(const sass::string& msg, const sass::string& prefix, const sass::string& middle, const bool trim) - { - int max_len = 18; - const char* end = this->end; - while (*end != 0) ++ end; - const char* pos = peek < optional_spaces >(); - if (!pos) pos = position; - - const char* last_pos(pos); - if (last_pos > begin) { - utf8::prior(last_pos, begin); - } - // backup position to last significant char - while (trim && last_pos > begin&& last_pos < end) { - if (!Util::ascii_isspace(static_cast(*last_pos))) break; - utf8::prior(last_pos, begin); - } - - bool ellipsis_left = false; - const char* pos_left(last_pos); - const char* end_left(last_pos); - - if (*pos_left) utf8::next(pos_left, end); - if (*end_left) utf8::next(end_left, end); - while (pos_left > begin) { - if (utf8::distance(pos_left, end_left) >= max_len) { - utf8::prior(pos_left, begin); - ellipsis_left = *(pos_left) != '\n' && - *(pos_left) != '\r'; - utf8::next(pos_left, end); - break; - } - - const char* prev = pos_left; - utf8::prior(prev, begin); - if (*prev == '\r') break; - if (*prev == '\n') break; - pos_left = prev; - } - if (pos_left < begin) { - pos_left = begin; - } - - bool ellipsis_right = false; - const char* end_right(pos); - const char* pos_right(pos); - while (end_right < end) { - if (utf8::distance(pos_right, end_right) > max_len) { - ellipsis_left = *(pos_right) != '\n' && - *(pos_right) != '\r'; - break; - } - if (*end_right == '\r') break; - if (*end_right == '\n') break; - utf8::next(end_right, end); - } - // if (*end_right == 0) end_right ++; - - sass::string left(pos_left, end_left); - sass::string right(pos_right, end_right); - size_t left_subpos = left.size() > 15 ? left.size() - 15 : 0; - size_t right_subpos = right.size() > 15 ? right.size() - 15 : 0; - if (left_subpos && ellipsis_left) left = ellipsis + left.substr(left_subpos); - if (right_subpos && ellipsis_right) right = right.substr(right_subpos) + ellipsis; - // now pass new message to the more generic error function - error(msg + prefix + quote(left) + middle + quote(right)); - } + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// } diff --git a/src/parser.hpp b/src/parser.hpp index 25c39b968f..f821057f21 100644 --- a/src/parser.hpp +++ b/src/parser.hpp @@ -1,394 +1,218 @@ -#ifndef SASS_PARSER_H -#define SASS_PARSER_H - -// sass.hpp must go before all system headers to get the -// __EXTENSIONS__ fix on Solaris. -#include "sass.hpp" - -#include -#include - -#include "ast.hpp" -#include "position.hpp" -#include "context.hpp" -#include "position.hpp" -#include "prelexer.hpp" -#include "source.hpp" - -#ifndef MAX_NESTING -// Note that this limit is not an exact science -// it depends on various factors, which some are -// not under our control (compile time or even OS -// dependent settings on the available stack size) -// It should fix most common segfault cases though. -#define MAX_NESTING 512 -#endif - -struct Lookahead { - const char* found; - const char* error; - const char* position; - bool parsable; - bool has_interpolants; - bool is_custom_property; -}; +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#ifndef SASS_PARSER_HPP +#define SASS_PARSER_HPP + +// sass.hpp must go before all system headers +// to get the __EXTENSIONS__ fix on Solaris. +#include "capi_sass.hpp" + +// Parser has a great chance for stack overflow +// To fix this once and for all we need a different approach +// http://lambda-the-ultimate.org/node/1599 +// Basically we should not call into recursion, but rather +// return some continuation bit, of course we still need to +// add our ast nodes somewhere (instead of returning as now) + +#include "interpolation.hpp" +#include "scanner_string.hpp" namespace Sass { - class Parser : public SourceSpan { + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + class Parser + { public: - enum Scope { Root, Mixin, Function, Media, Control, Properties, Rules, AtRoot }; + // Compiler context + Compiler& compiler; - Context& ctx; - sass::vector block_stack; - sass::vector stack; - SourceDataObj source; - const char* begin; - const char* position; - const char* end; - Offset before_token; - Offset after_token; - SourceSpan pstate; - Backtraces traces; - size_t indentation; - size_t nestings; - bool allow_parent; - Token lexed; + // Alias into context + Root*& modctx; - Parser(SourceData* source, Context& ctx, Backtraces, bool allow_parent = true); + sass::vector modules; - // special static parsers to convert strings into certain selectors - static SelectorListObj parse_selector(SourceData* source, Context& ctx, Backtraces, bool allow_parent = true); + // Alias into context + WithConfig*& wconfig; -#ifdef __clang__ + // bool& hasWithConfig; - // lex and peak uses the template parameter to branch on the action, which - // triggers clangs tautological comparison on the single-comparison - // branches. This is not a bug, just a merging of behaviour into - // one function + // The scanner that scans through the text being parsed. + StringScanner scanner; -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wtautological-compare" + protected: -#endif + // Value constructor + Parser( + Compiler& context, + SourceDataObj source); + friend class ExpressionParser; - // skip current token and next whitespace - // moves SourceSpan right before next token - void advanceToNextToken(); + // Original source mappings. If defined, SourceSpans will + // point to where the original source has come from. + // sass::vector srcmap; - bool peek_newline(const char* start = 0); + // Points to context.varStack + sass::vector& varStack; - // skip over spaces, tabs and line comments - template - const char* sneak(const char* start = 0) - { - using namespace Prelexer; - - // maybe use optional start position from arguments? - const char* it_position = start ? start : position; - - // skip white-space? - if (mx == spaces || - mx == no_spaces || - mx == css_comments || - mx == css_whitespace || - mx == optional_spaces || - mx == optional_css_comments || - mx == optional_css_whitespace - ) { - return it_position; - } + // The silent comment this parser encountered previously. + SilentCommentObj lastSilentComment; + + protected: - // skip over spaces, tabs and sass line comments - const char* pos = optional_css_whitespace(it_position); - // always return a valid position - return pos ? pos : it_position; + // Returns whether [text] is a valid CSS identifier. + bool isIdentifier(sass::string text); + // Consumes whitespace, including any comments. + virtual void scanWhitespace() + { + do { + scanWhitespaceWithoutComments(); + } while (scanComment()); } - // match will not skip over space, tabs and line comment - // return the position where the lexer match will occur - template - const char* match(const char* start = 0) + virtual void expectWhitespace() { - // match the given prelexer - return mx(position); + if (scanner.isDone() || !(Character::isWhitespace(scanner.peekChar()) || scanComment())) { + error("Expected whitespace.", scanner.rawSpan()); + } + scanWhitespace(); } - // peek will only skip over space, tabs and line comment - // return the position where the lexer match will occur - template - const char* peek(const char* start = 0) + // Consumes whitespace, but not comments. + virtual void scanWhitespaceWithoutComments() { + while (!scanner.isDone() && Character::isWhitespace(scanner.peekChar())) { + scanner.readChar(); + } + } - // sneak up to the actual token we want to lex - // this should skip over white-space if desired - const char* it_before_token = sneak < mx >(start); + // Consumes spaces and tabs. + inline void scanSpaces() + { + while (!scanner.isDone() && Character::isSpaceOrTab(scanner.peekChar())) { + scanner.readChar(); + } + } - // match the given prelexer - const char* match = mx(it_before_token); + // Consumes and ignores a comment if possible. + // Returns whether the comment was consumed. + bool scanComment(); - // check if match is in valid range - return match <= end ? match : 0; + // Consumes and ignores a loud (CSS-style) comment. + virtual void scanLoudComment(); - } + // Consumes and ignores a silent (Sass-style) comment. + virtual void scanSilentComment(); - // white-space handling is built into the lexer - // this way you do not need to parse it yourself - // some matchers don't accept certain white-space - // we do not support start arg, since we manipulate - // sourcemap offset and we modify the position pointer! - // lex will only skip over space, tabs and line comment - template - const char* lex(bool lazy = true, bool force = false) - { + // Consumes a plain CSS identifier. If [unit] is `true`, this + // doesn't parse a `-` followed by a digit. This ensures that + // `1px-2px` parses as subtraction rather than the unit `px-2px`. + sass::string readIdentifier(bool unit = false); - if (*position == 0) return 0; + // Consumes a chunk of a plain CSS identifier after the name start. + sass::string identifierBody(); - // position considered before lexed token - // we can skip whitespace or comments for - // lazy developers (but we need control) - const char* it_before_token = position; + // Like [consumeIdentifierBody], but parses the body into the [text] buffer. + void consumeIdentifierBody(StringBuffer& text, bool unit = false); - // sneak up to the actual token we want to lex - // this should skip over white-space if desired - if (lazy) it_before_token = sneak < mx >(position); + // Consumes a plain CSS string. This returns the parsed contents of the + // string—that is, it doesn't include quotes and its escapes are resolved. + sass::string string(); - // now call matcher to get position after token - const char* it_after_token = mx(it_before_token); + // Consumes and returns a natural number. + // That is, a non - negative integer. + // Doesn't support scientific notation. + double naturalNumber(); - // check if match is in valid range - if (it_after_token > end) return 0; + // Consumes tokens until it reaches a top-level `";"`, `")"`, `"]"`, + // or `"}"` and returns their contents as a string. If [allowEmpty] + // is `false` (the default), this requires at least one token. + sass::string declarationValue(bool allowEmpty = false); - // maybe we want to update the parser state anyway? - if (force == false) { - // assertion that we got a valid match - if (it_after_token == 0) return 0; - // assertion that we actually lexed something - if (it_after_token == it_before_token) return 0; - } + // Consumes a `url()` token if possible, and returns `null` otherwise. + sass::string tryUrl(); - // create new lexed token object (holds the parse results) - lexed = Token(position, it_before_token, it_after_token); + // Consumes a Sass variable name, and returns + // its name without the dollar sign. + sass::string variableName(); - // advance position (add whitespace before current token) - before_token = after_token.add(position, it_before_token); + // Consumes an escape sequence and returns the text that defines it. + // If [identifierStart] is true, this normalizes the escape sequence + // as though it were at the beginning of an identifier. + void escape(StringBuffer& buffer, bool identifierStart = false); - // update after_token position for current token - after_token.add(it_before_token, it_after_token); + // Consumes an escape sequence and returns the character it represents. + uint32_t escapeCharacter(); - // ToDo: could probably do this incremental on original object (API wants offset?) - pstate = SourceSpan(source, before_token, after_token - before_token); + // Consumes the next character if it matches [condition]. + // Returns whether or not the character was consumed. + bool scanCharIf(bool (*condition)(uint8_t character)); - // advance internal char iterator - return position = it_after_token; + // Consumes the next character or escape sequence if it matches [expected]. + // Matching will be case-insensitive unless [caseSensitive] is true. + bool scanIdentCharInsensitive(uint8_t letter); + bool scanIdentCharSensitive(uint8_t letter); + bool scanIdentChar(uint8_t letter, bool sensitive = false) { + if (sensitive) return scanIdentCharSensitive(letter); + else return scanIdentCharInsensitive(letter); } - // lex_css skips over space, tabs, line and block comment - // all block comments will be consumed and thrown away - // source-map position will point to token after the comment - template - const char* lex_css() - { - // copy old token - Token prev = lexed; - // store previous pointer - const char* oldpos = position; - Offset bt = before_token; - Offset at = after_token; - SourceSpan op = pstate; - // throw away comments - // update srcmap position - lex < Prelexer::css_comments >(); - // now lex a new token - const char* pos = lex< mx >(); - // maybe restore prev state - if (pos == 0) { - pstate = op; - lexed = prev; - position = oldpos; - after_token = at; - before_token = bt; - } - // return match - return pos; - } + // Consumes the next character and asserts that + // it's equal to [letter], ignoring ASCII case. + void expectIdentCharInsensitive(uint8_t letter); + void expectIdentCharSensitive(uint8_t letter); - // all block comments will be skipped and thrown away - template - const char* peek_css(const char* start = 0) - { - // now peek a token (skip comments first) - return peek< mx >(peek < Prelexer::css_comments >(start)); + void expectIdentChar(uint8_t letter, bool sensitive = false) { + if (sensitive) return expectIdentCharSensitive(letter); + else return expectIdentCharInsensitive(letter); } -#ifdef __clang__ + // Returns whether the scanner is immediately before a number. This follows [the CSS algorithm]. + // [the CSS algorithm]: https://drafts.csswg.org/css-syntax-3/#starts-with-a-number + bool lookingAtNumber() const; -#pragma clang diagnostic pop + // Returns whether the scanner is immediately before a plain CSS identifier. + // If [forward] is passed, this looks that many characters forward instead. + // This is based on [the CSS algorithm][], but it assumes all backslashes start escapes. + // [the CSS algorithm]: https://drafts.csswg.org/css-syntax-3/#would-start-an-identifier + bool lookingAtIdentifier(size_t forward = 0) const; -#endif + // Returns whether the scanner is immediately before a sequence + // of characters that could be part of a plain CSS identifier body. + bool lookingAtIdentifierBody(); + + // Consumes an identifier if its name exactly matches [text]. + bool scanIdentifier(const char* text, bool sensitive = false); + bool scanIdentifier(sass::string text, bool sensitive = false); - void error(sass::string msg); - // generate message with given and expected sample - // text before and in the middle are configurable - void css_error(const sass::string& msg, - const sass::string& prefix = " after ", - const sass::string& middle = ", was: ", - const bool trim = true); - void read_bom(); - - Block_Obj parse(); - Import_Obj parse_import(); - Definition_Obj parse_definition(Definition::Type which_type); - Parameters_Obj parse_parameters(); - Parameter_Obj parse_parameter(); - Mixin_Call_Obj parse_include_directive(); - Arguments_Obj parse_arguments(); - Argument_Obj parse_argument(); - Assignment_Obj parse_assignment(); - StyleRuleObj parse_ruleset(Lookahead lookahead); - SelectorListObj parseSelectorList(bool chroot); - ComplexSelectorObj parseComplexSelector(bool chroot); - Selector_Schema_Obj parse_selector_schema(const char* end_of_selector, bool chroot); - CompoundSelectorObj parseCompoundSelector(); - SimpleSelectorObj parse_simple_selector(); - PseudoSelectorObj parse_negated_selector2(); - Expression* parse_binominal(); - SimpleSelectorObj parse_pseudo_selector(); - AttributeSelectorObj parse_attribute_selector(); - Block_Obj parse_block(bool is_root = false); - Block_Obj parse_css_block(bool is_root = false); - bool parse_block_nodes(bool is_root = false); - bool parse_block_node(bool is_root = false); - - Declaration_Obj parse_declaration(); - ExpressionObj parse_map(); - ExpressionObj parse_bracket_list(); - ExpressionObj parse_list(bool delayed = false); - ExpressionObj parse_comma_list(bool delayed = false); - ExpressionObj parse_space_list(); - ExpressionObj parse_disjunction(); - ExpressionObj parse_conjunction(); - ExpressionObj parse_relation(); - ExpressionObj parse_expression(); - ExpressionObj parse_operators(); - ExpressionObj parse_factor(); - ExpressionObj parse_value(); - Function_Call_Obj parse_calc_function(); - Function_Call_Obj parse_function_call(); - Function_Call_Obj parse_function_call_schema(); - String_Obj parse_url_function_string(); - String_Obj parse_url_function_argument(); - String_Obj parse_interpolated_chunk(Token, bool constant = false, bool css = true); - String_Obj parse_string(); - ValueObj parse_static_value(); - String_Schema_Obj parse_css_variable_value(); - String_Obj parse_ie_property(); - String_Obj parse_ie_keyword_arg(); - String_Schema_Obj parse_value_schema(const char* stop); - String_Obj parse_identifier_schema(); - If_Obj parse_if_directive(bool else_if = false); - ForRuleObj parse_for_directive(); - EachRuleObj parse_each_directive(); - WhileRuleObj parse_while_directive(); - MediaRule_Obj parseMediaRule(); - sass::vector parseCssMediaQueries(); - sass::string parseIdentifier(); - CssMediaQuery_Obj parseCssMediaQuery(); - Return_Obj parse_return_directive(); - Content_Obj parse_content_directive(); - void parse_charset_directive(); - List_Obj parse_media_queries(); - Media_Query_Obj parse_media_query(); - Media_Query_ExpressionObj parse_media_expression(); - SupportsRuleObj parse_supports_directive(); - SupportsConditionObj parse_supports_condition(bool top_level); - SupportsConditionObj parse_supports_negation(); - SupportsConditionObj parse_supports_operator(bool top_level); - SupportsConditionObj parse_supports_interpolation(); - SupportsConditionObj parse_supports_declaration(); - SupportsConditionObj parse_supports_condition_in_parens(bool parens_required); - AtRootRuleObj parse_at_root_block(); - At_Root_Query_Obj parse_at_root_query(); - String_Schema_Obj parse_almost_any_value(); - AtRuleObj parse_directive(); - WarningRuleObj parse_warning(); - ErrorRuleObj parse_error(); - DebugRuleObj parse_debug(); - - Value* color_or_string(const sass::string& lexed) const; - - // be more like ruby sass - ExpressionObj lex_almost_any_value_token(); - ExpressionObj lex_almost_any_value_chars(); - ExpressionObj lex_interp_string(); - ExpressionObj lex_interp_uri(); - ExpressionObj lex_interpolation(); - - // these will throw errors - Token lex_variable(); - Token lex_identifier(); - - void parse_block_comments(bool store = true); - - Lookahead lookahead_for_value(const char* start = 0); - Lookahead lookahead_for_selector(const char* start = 0); - Lookahead lookahead_for_include(const char* start = 0); - - ExpressionObj fold_operands(ExpressionObj base, sass::vector& operands, Operand op); - ExpressionObj fold_operands(ExpressionObj base, sass::vector& operands, sass::vector& ops, size_t i = 0); - - void throw_syntax_error(sass::string message, size_t ln = 0); - void throw_read_error(sass::string message, size_t ln = 0); - - - template - ExpressionObj lex_interp() + // ToDo: make template to ignore return type + template + sass::string rawText(T(X::* consumer)()) { - if (lex < open >(false)) { - String_Schema_Obj schema = SASS_MEMORY_NEW(String_Schema, pstate); - // std::cerr << "LEX [[" << sass::string(lexed) << "]]\n"; - schema->append(SASS_MEMORY_NEW(String_Constant, pstate, lexed)); - if (position[0] == '#' && position[1] == '{') { - ExpressionObj itpl = lex_interpolation(); - if (!itpl.isNull()) schema->append(itpl); - while (lex < close >(false)) { - // std::cerr << "LEX [[" << sass::string(lexed) << "]]\n"; - schema->append(SASS_MEMORY_NEW(String_Constant, pstate, lexed)); - if (position[0] == '#' && position[1] == '{') { - ExpressionObj itpl = lex_interpolation(); - if (!itpl.isNull()) schema->append(itpl); - } else { - return schema; - } - } - } else { - return SASS_MEMORY_NEW(String_Constant, pstate, lexed); - } - } - return {}; + const char* start = scanner.position; + // We need to clean up after ourself + // This has a chance to leak memory! + (static_cast(this)->*consumer)(); + return scanner.substring(start); } - public: - static Number* lexed_number(const SourceSpan& pstate, const sass::string& parsed); - static Number* lexed_dimension(const SourceSpan& pstate, const sass::string& parsed); - static Number* lexed_percentage(const SourceSpan& pstate, const sass::string& parsed); - static Value* lexed_hex_color(const SourceSpan& pstate, const sass::string& parsed); - private: - Number* lexed_number(const sass::string& parsed) { return lexed_number(pstate, parsed); }; - Number* lexed_dimension(const sass::string& parsed) { return lexed_dimension(pstate, parsed); }; - Number* lexed_percentage(const sass::string& parsed) { return lexed_percentage(pstate, parsed); }; - Value* lexed_hex_color(const sass::string& parsed) { return lexed_hex_color(pstate, parsed); }; - - static const char* re_attr_sensitive_close(const char* src); - static const char* re_attr_insensitive_close(const char* src); + // Consumes an identifier and asserts that its name exactly matches [text]. + void expectIdentifier(const char* text, sass::string name, bool sensitive = false); + + // Throws an error associated with [span]. + void error(sass::string message, SourceSpan pstate /*, FileSpan span */); }; - size_t check_bom_chars(const char* src, const char *end, const unsigned char* bom, size_t len); + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + } #endif diff --git a/src/parser_at_root_query.cpp b/src/parser_at_root_query.cpp new file mode 100644 index 0000000000..385b9abca9 --- /dev/null +++ b/src/parser_at_root_query.cpp @@ -0,0 +1,57 @@ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +/* Implementations are mostly a direct code-port from dart-sass. */ +/*****************************************************************************/ +#include "parser_at_root_query.hpp" + +#include "character.hpp" + +namespace Sass { + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // Import some namespaces + using namespace Charcode; + using namespace Character; + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + AtRootQuery* AtRootQueryParser::parse() + { + Offset start(scanner.offset); + scanner.expectChar($lparen); + scanWhitespace(); + + bool include = scanIdentifier("with"); + if (!include) expectIdentifier("without", + "\"with\" or \"without\""); + + scanWhitespace(); + scanner.expectChar($colon); + scanWhitespace(); + + StringSet atRules; + do { + sass::string ident(readIdentifier()); + StringUtils::makeLowerCase(ident); + atRules.insert(ident); + scanWhitespace(); + } + while (lookingAtIdentifier()); + + scanner.expectChar($rparen); + scanner.expectDone(); + + return SASS_MEMORY_NEW(AtRootQuery, + scanner.rawSpanFrom(start), + std::move(atRules), + include); + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + +} diff --git a/src/parser_at_root_query.hpp b/src/parser_at_root_query.hpp new file mode 100644 index 0000000000..bf16d95a5f --- /dev/null +++ b/src/parser_at_root_query.hpp @@ -0,0 +1,39 @@ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#ifndef SASS_PARSER_AT_ROOT_QUERY_HPP +#define SASS_PARSER_AT_ROOT_QUERY_HPP + +// sass.hpp must go before all system headers +// to get the __EXTENSIONS__ fix on Solaris. +#include "capi_sass.hpp" + +#include "parser.hpp" + +namespace Sass { + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + class AtRootQueryParser : public Parser + { + public: + + // Value constructor + AtRootQueryParser( + Compiler& context, + SourceDataObj source) : + Parser(context, source) + {} + + // Main entry function + AtRootQuery* parse(); + + }; + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + +} + +#endif diff --git a/src/parser_base.cpp b/src/parser_base.cpp new file mode 100644 index 0000000000..1005a002aa --- /dev/null +++ b/src/parser_base.cpp @@ -0,0 +1,6 @@ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#include "parser_base.hpp" + +// Nothing to see here yet diff --git a/src/parser_base.hpp b/src/parser_base.hpp new file mode 100644 index 0000000000..627f12227f --- /dev/null +++ b/src/parser_base.hpp @@ -0,0 +1,36 @@ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#ifndef SASS_PARSER_BASE_HPP +#define SASS_PARSER_BASE_HPP + +// sass.hpp must go before all system headers +// to get the __EXTENSIONS__ fix on Solaris. +#include "capi_sass.hpp" + +#include "parser.hpp" + +namespace Sass { + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + class BaseParser : public Parser + { + public: + + // Value constructor + BaseParser( + Compiler& context, + SourceDataObj source) : + Parser(context, source) + {} + + }; + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + +} + +#endif diff --git a/src/parser_css.cpp b/src/parser_css.cpp new file mode 100644 index 0000000000..f2d116963e --- /dev/null +++ b/src/parser_css.cpp @@ -0,0 +1,309 @@ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +/* Implementations are mostly a direct code-port from dart-sass. */ +/*****************************************************************************/ +#include "parser_css.hpp" + +#include "character.hpp" +#include "ast_imports.hpp" +#include "ast_expressions.hpp" + +namespace Sass { + + // Import some namespaces + using namespace Charcode; + using namespace Character; + + // Consumes a plain-CSS `@import` rule that disallows + // interpolation. [start] should point before the `@`. + ImportRule* CssParser::readImportRule(Offset start) + { + uint8_t next = scanner.peekChar(); + ExpressionObj url; + if (next == $u || next == $U) { + url = readFunctionOrStringExpression(); + } + else { + StringExpressionObj ex(readInterpolatedString()); + InterpolationObj itpl(ex->getAsInterpolation()); + url = SASS_MEMORY_NEW(StringExpression, ex->pstate(), itpl); + } + scanWhitespace(); + Offset beforeQuery(scanner.offset); + auto modifiers(tryImportModifiers()); + // auto queries(tryImportQueries()); + expectStatementSeparator("@import rule"); + SourceSpan span(scanner.relevantSpanFrom(beforeQuery)); + ImportRuleObj imp(SASS_MEMORY_NEW(ImportRule, span)); + StaticImportObj entry(SASS_MEMORY_NEW(StaticImport, span, + SASS_MEMORY_NEW(Interpolation, span, url), modifiers, + true)); + entry->outOfOrder(false); + imp->append(entry.ptr()); + return imp.detach(); + } + // EO readImportRule + + // Consume a silent comment and throws error + SilentComment* CssParser::readSilentComment() + { + Offset start(scanner.offset); + lastSilentComment = ScssParser::readSilentComment(); + error("Silent comments aren't allowed in plain CSS.", + scanner.relevantSpanFrom(start)); + return lastSilentComment.detach(); + } + // EO readSilentComment + + // Consume a silent comment and throws error + void CssParser::scanSilentComment() + { + Offset start(scanner.offset); + lastSilentComment = ScssParser::readSilentComment(); + error("Silent comments aren't allowed in plain CSS.", + scanner.relevantSpanFrom(start)); + } + // EO readSilentComment + + // Helper to declare all forbidden at-rules + bool isForbiddenCssAtRule(const sass::string& name) + { + return name == "at-root" + || name == "content" + || name == "debug" + || name == "each" + || name == "error" + || name == "extend" + || name == "for" + || name == "function" + || name == "if" + || name == "include" + || name == "mixin" + || name == "return" + || name == "warn" + || name == "while"; + } + // EO isForbiddenCssAtRule + + // Parse allowed at-rule statement and parse children via [child_parser] parser function + Statement* CssParser::readAtRule(Statement* (StylesheetParser::* child)(), bool root) + { + // NOTE: logic is largely duplicated in CssParser.atRule. + // Most changes here should be mirrored there. + + Offset start(scanner.offset); + scanner.expectChar($at); + InterpolationObj name = readInterpolatedIdentifier(); + scanWhitespace(); + + sass::string plain(name->getPlainString()); + + if (isForbiddenCssAtRule(plain)) { + InterpolationObj value = readAlmostAnyValue(); + error("This at-rule isn't allowed in plain CSS.", + scanner.relevantSpanFrom(start)); + } + else if (plain == "charset") { + sass::string charset(string()); + if (!root) { + error("This at-rule is not allowed here.", + scanner.relevantSpanFrom(start)); + } + } + else if (plain == "import") { + return readImportRule(start); + } + else if (plain == "media") { + return readMediaRule(start); + } + else if (plain == "-moz-document") { + return readMozDocumentRule(start, name); + } + else if (plain == "supports") { + return readSupportsRule(start); + } + else { + return readAnyAtRule(start, name); + } + + return nullptr; + } + // EO readAtRule + + // Helper to declare all forbidden functions + bool isDisallowedFunction(sass::string name) + { + return name == str_red + || name == str_green + || name == str_blue + || name == str_mix + || name == str_hue + || name == str_saturation + || name == str_lightness + || name == str_adjust_hue + || name == str_lighten + || name == str_darken + || name == str_desaturate + || name == str_complement + || name == str_opacify + || name == str_fade_in + || name == str_transparentize + || name == str_fade_out + || name == str_adjust_color + || name == str_scale_color + || name == str_change_color + || name == str_ie_hex_str + || name == str_unquote + || name == str_quote + || name == str_str_length + || name == str_str_insert + || name == str_str_index + || name == str_str_slice + || name == str_to_upper_case + || name == str_to_lower_case + || name == str_percentage + || name == str_round + || name == str_ceil + || name == str_floor + || name == str_abs + || name == str_max + || name == str_min + || name == str_random + || name == str_length + || name == str_nth + || name == str_set_nth + || name == str_join + || name == str_append + || name == str_zip + || name == str_index + || name == str_list_separator + || name == str_is_bracketed + || name == str_map_get + || name == str_map_merge + || name == str_map_remove + || name == str_map_keys + || name == str_map_values + || name == str_map_has_key + || name == str_keywords + || name == str_selector_nest + || name == str_selector_append + || name == str_selector_extend + || name == str_selector_replace + || name == str_selector_unify + || name == str_is_superselector + || name == str_simple_selectors + || name == str_selector_parse + || name == str_feature_exists + || name == str_inspect + || name == str_type_of + || name == str_unit + || name == str_unitless + || name == str_comparable + || name == str_whiteness + || name == str_blackness + || name == str_if + || name == str_unique_id; + } + // EO isDisallowedFunction + + // Expression* CssParser::namespacedExpression(sass::string ns, Offset start) { + // auto expression = StylesheetParser::readNamespacedExpression(ns, start); + // error("Module namespaces aren't allowed in plain CSS.", expression->pstate()); + // return nullptr; + // } + + Expression* CssParser::readParenthesizedExpression() { + // Expressions are only allowed within calculations, but we verify this at + // evaluation time. + Offset start(scanner.offset); + scanner.expectChar($lparen); + scanWhitespace(); + Expression* expression = readExpressionUntilComma(); + scanner.expectChar($rparen); + return SASS_MEMORY_NEW(ParenthesizedExpression, + scanner.relevantSpanFrom(start), expression); + } + + + // Consumes an expression that starts like an identifier. + Expression* CssParser::readIdentifierLike() + { + Offset start(scanner.offset); + InterpolationObj identifier = readInterpolatedIdentifier(); + sass::string plain(identifier->getPlainString()); + auto lower = StringUtils::toLowerCase(plain); + StringExpressionObj specialFunction = trySpecialFunction(lower, start); + + if (specialFunction != nullptr) { + return specialFunction.detach(); + } + + // Offset beforeArguments(scanner.offset); + + if (scanner.scanChar($dot)) { + return readNamespacedExpression(plain, start); + } + + if (!scanner.scanChar($lparen)) { + return SASS_MEMORY_NEW(StringExpression, + scanner.rawSpanFrom(start), identifier); + } + + bool allowEmptySecondArg = (lower == "var"); + + if (auto specialFunction = trySpecialFunction(lower, start)) { + return specialFunction; + } + + ExpressionVector arguments; + + if (!scanner.scanChar($rparen)) { + do { + scanWhitespace(); + if (allowEmptySecondArg && arguments.size() == 1 && scanner.peekChar() == $rparen) { + arguments.push_back(SASS_MEMORY_NEW(StringExpression, scanner.rawSpan(), "")); + break; + } + Expression* argument = readExpressionUntilComma(true); + if (argument) arguments.emplace_back(argument); + scanWhitespace(); + } while (scanner.scanChar($comma)); + scanner.expectChar($rparen); + } + + // arguments->pstate(scanner.relevantSpan(start)); + + if (isDisallowedFunction(plain)) { + error( + "This function isn't allowed in plain CSS.", + scanner.relevantSpanFrom(start)); + } + + Interpolation* name = SASS_MEMORY_NEW(Interpolation, identifier->pstate()); + name->append(SASS_MEMORY_NEW(StringExpression, identifier->pstate(), identifier)); + + CallableArguments* args = SASS_MEMORY_NEW(CallableArguments, + scanner.rawSpanFrom(start), std::move(arguments), {}); + + // Plain Css as it's interpolated + // if (name->getPlainString().empty()) { + // return SASS_MEMORY_NEW(ItplFnExpression, + // scanner.relevantSpanFrom(start), name, args, ""); + // } + + return SASS_MEMORY_NEW(FunctionExpression, + scanner.rawSpanFrom(start), plain, args); + + } + // EO readIdentifierLike + + Expression* CssParser::readNamespacedExpression( + const sass::string& ns, Offset start) + { + SourceSpan pstate(scanner.relevantSpanFrom(start)); + ExpressionObj expression = ScssParser::readNamespacedExpression(ns, start); + error("Module namespaces aren't allowed in plain CSS.", pstate); + return expression; + } +} diff --git a/src/parser_css.hpp b/src/parser_css.hpp new file mode 100644 index 0000000000..83f539dcdc --- /dev/null +++ b/src/parser_css.hpp @@ -0,0 +1,63 @@ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#ifndef SASS_PARSER_CSS_HPP +#define SASS_PARSER_CSS_HPP + +// sass.hpp must go before all system headers +// to get the __EXTENSIONS__ fix on Solaris. +#include "capi_sass.hpp" + +#include "parser_scss.hpp" + +namespace Sass { + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + class CssParser : public ScssParser + { + public: + + // Value constructor + CssParser( + Compiler& context, + SourceDataObj source) : + ScssParser(context, source) + {} + + protected: + + // Whether this is a plain CSS stylesheet. + bool plainCss() const override final { return true; } + + // Consumes a plain-CSS `@import` rule that disallows + // interpolation. [start] should point before the `@`. + ImportRule* readImportRule(Offset start); + + // Expression* namespacedExpression(sass::string ns, Offset start); + + // Consumes an expression that starts like an identifier. + Expression* readIdentifierLike() override final; + + // Consume a silent comment and throws error + SilentComment* readSilentComment() override final; + + // Consume a silent comment and throws error + void scanSilentComment() override final; + + // Parse allowed at-rule statement and parse children via [child_parser] parser function + Statement* readAtRule(Statement* (StylesheetParser::* child_parser)(), bool root = false) override final; + + Expression* readNamespacedExpression(const sass::string& ns, Offset start) override final; + + Expression* readParenthesizedExpression() override final; + + }; + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + +} + +#endif diff --git a/src/parser_expression.cpp b/src/parser_expression.cpp new file mode 100644 index 0000000000..209a0f9491 --- /dev/null +++ b/src/parser_expression.cpp @@ -0,0 +1,207 @@ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +/* Implementations are mostly a direct code-port from dart-sass. */ +/*****************************************************************************/ +#include "parser_expression.hpp" + +#include "character.hpp" +#include "utf8/checked.h" +#include "ast_values.hpp" +#include "ast_expressions.hpp" + +namespace Sass { + + // Import some namespaces + using namespace Charcode; + using namespace Character; + + void ExpressionParser::addOperator(SassOperator op, Offset& start) { + if (parser.plainCss() && op != SassOperator::ASSIGN && + // These are allowed in calculations, so + // we have to check them at evaluation time. + op != SassOperator::ADD && + op != SassOperator::SUB && + op != SassOperator::MUL && + op != SassOperator::DIV) { + parser.error("Operators aren't allowed in plain CSS.", + parser.scanner.relevantSpanFrom(start)); + /* , + position: scanner.position - operator.operator.length, + length : operator.operator.length */ + } + + allowSlash = allowSlash && op == SassOperator::DIV; // done + while ((!operators.empty()) && + (sass_op_to_precedence(operators.back()) + >= sass_op_to_precedence(op))) { + resolveOneOperation(); + } + + //std::cerr << "Add operator, prev [" << parser.scanner.peekChar(-2) << "], next [" << parser.scanner.peekChar(0) << "]\n"; + + // ToDo: merge into one structure? + operators.emplace_back(op); + opstates.emplace_back(parser.scanner.relevantSpanFrom(start)); + bool isSafe = Character::isWhitespace(parser.scanner.peekChar(-2)); + isSafe &= Character::isWhitespace(parser.scanner.peekChar(0)); + calcSafe.push_back(isSafe); + + // We started parsing with an operator + if (singleExpression == nullptr) { + SourceSpan pstate(parser.scanner.relevantSpanFrom(start)); + singleExpression = SASS_MEMORY_NEW(NullExpression, + pstate, SASS_MEMORY_NEW(Null, pstate)); + } + + // assert(singleExpression != null); + + operands.emplace_back(singleExpression); + parser.scanWhitespace(); + // allowSlash = allowSlash && parser.lookingAtNumber(); + singleExpression = parser.readSingleExpression(); + // allowSlash = allowSlash && singleExpression->isaNumberExpression(); + } + + ExpressionParser::ExpressionParser(StylesheetParser& parser) : + start(parser.scanner.state()), + commaExpressions(), + singleEqualsOperand(), + spaceExpressions(), + operators(), + opstates(), + calcSafe(), + operands(), + allowSlash(true), + singleExpression(), + parser(parser) + { + // allowSlash = parser.lookingAtNumber(); + singleExpression = parser.readSingleExpression(); + } + + void ExpressionParser::resetState() + { + commaExpressions.clear(); + spaceExpressions.clear(); + operators.clear(); + opstates.clear(); + calcSafe.clear(); + operands.clear(); + parser.scanner.backtrack(start); + allowSlash = true; // parser.lookingAtNumber(); + singleExpression = parser.readSingleExpression(); + } + + bool _isSlashOperand(Expression* expression) { + if (expression->isaNumberExpression()) return true; + if (expression->isaFunctionExpression()) return true; + if (auto* op = expression->isaBinaryOpExpression()) { + return op->allowsSlash(); + } + return false; + } + + void ExpressionParser::resolveOneOperation() + { + enum SassOperator op = operators.back(); + SourceSpan opstate = opstates.back(); + bool isCalcSafe = calcSafe.back(); + // auto start(parser.scanner.offset); + operators.pop_back(); + opstates.pop_back(); + calcSafe.pop_back(); + + auto left = operands.back(); + operands.pop_back(); + + auto right = singleExpression; + + if (allowSlash && !parser.inParentheses && op == SassOperator::DIV + && _isSlashOperand(left) && _isSlashOperand(right)) { + + singleExpression = SASS_MEMORY_NEW(BinaryOpExpression, + SourceSpan::delta(left->pstate(), right->pstate()), + op, std::move(opstate), left, right, true, isCalcSafe); + + } + else { + singleExpression = SASS_MEMORY_NEW(BinaryOpExpression, + SourceSpan::delta(left->pstate(), right->pstate()), + op, std::move(opstate), left, right, allowSlash = false, isCalcSafe); + + if (op == SassOperator::ADD || op == SassOperator::SUB) { + // todo warn for deprecation + } + + } + } + + void ExpressionParser::resolveOperations() + { + if (operators.empty()) return; + while (!operators.empty()) { + resolveOneOperation(); + } + } + + void ExpressionParser::addSingleExpression(ExpressionObj expression, bool number) + { + if (singleExpression != nullptr) { + // If we discover we're parsing a list whose first element is a division + // operation, and we're in parentheses, re-parse outside of a parent + // context. This ensures that `(1/2 1)` doesn't perform division on its + // first element. + if (parser.inParentheses) { + parser.inParentheses = false; + if (allowSlash) { + resetState(); + return; + } + } + + resolveOperations(); + spaceExpressions.emplace_back(singleExpression); + allowSlash = true; + } + singleExpression = expression; + + } + + void ExpressionParser::resolveSpaceExpressions() { + resolveOperations(); + + if (!spaceExpressions.empty()) { + spaceExpressions.emplace_back(singleExpression); + SourceSpan span = SourceSpan::delta( + spaceExpressions.front()->pstate(), + spaceExpressions.back()->pstate()); + ListExpression* list = SASS_MEMORY_NEW( + ListExpression, std::move(span), SASS_SPACE); + list->concat(std::move(spaceExpressions)); + singleExpression = list; + spaceExpressions.clear(); + } + + if (singleEqualsOperand && singleExpression) { + singleExpression = SASS_MEMORY_NEW(BinaryOpExpression, + SourceSpan::delta(singleEqualsOperand->pstate(), singleExpression->pstate()), + SassOperator::IESEQ, parser.scanner.rawSpan(), + singleEqualsOperand, singleExpression); + singleEqualsOperand = {}; + + } + /* + // Seem to be for ms stuff + singleExpression = SASS_MEMORY_NEW(BinaryOpExpression, + "[pstateS2]", ) + + BinaryOperationExpression( + BinaryOperator.singleEquals, singleEqualsOperand, singleExpression); + singleEqualsOperand = null; + } + */ + + } + +} diff --git a/src/parser_expression.hpp b/src/parser_expression.hpp new file mode 100644 index 0000000000..d5a1b21be4 --- /dev/null +++ b/src/parser_expression.hpp @@ -0,0 +1,84 @@ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#ifndef SASS_PARSER_EXPRESSION_HPP +#define SASS_PARSER_EXPRESSION_HPP + +// sass.hpp must go before all system headers +// to get the __EXTENSIONS__ fix on Solaris. +#include "capi_sass.hpp" + +#include "scanner_string.hpp" +#include "parser_expression.hpp" +#include "parser_stylesheet.hpp" + +namespace Sass { + + // Helper class for stylesheet parser + class ExpressionParser final { + + friend class StylesheetParser; + friend class ScssParser; + friend class CssParser; + + public: + + // Value constructor + ExpressionParser( + StylesheetParser& parser); + + protected: + + StringScannerState start; + + ExpressionVector commaExpressions; + + ExpressionObj singleEqualsOperand; + + ExpressionVector spaceExpressions; + + // Operators whose right-hand operands are not fully parsed yet, in order of + // appearance in the document. Because a low-precedence operator will cause + // parsing to finish for all preceding higher-precedence operators, this is + // naturally ordered from lowest to highest precedence. + sass::vector operators; + sass::vector opstates; + sass::vector calcSafe; + + // The left-hand sides of [operators]. `operands[n]` + // is the left-hand side of `operators[n]`. + ExpressionVector operands; + + // Whether the single expression parsed so far + // may be interpreted as slash-separated numbers. + bool allowSlash = true; + + /// The leftmost expression that's been fully-parsed. Never `null`. + ExpressionObj singleExpression; + + // The associated parser + StylesheetParser& parser; + + // Resets the scanner state to the state it was at the + // beginning of the expression, except for [_inParentheses]. + void resetState(); + + void resolveOneOperation(); + + void resolveOperations(); + + void addSingleExpression( + ExpressionObj expression, + bool number = false); + + void addOperator( + SassOperator op, + Offset& start); + + void resolveSpaceExpressions(); + + }; + +} + +#endif diff --git a/src/parser_keyframe_selector.cpp b/src/parser_keyframe_selector.cpp new file mode 100644 index 0000000000..e11d4fb6bd --- /dev/null +++ b/src/parser_keyframe_selector.cpp @@ -0,0 +1,97 @@ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +/* Implementations are mostly a direct code-port from dart-sass. */ +/*****************************************************************************/ +#include "parser_keyframe_selector.hpp" + +#include "character.hpp" + +namespace Sass { + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // Import some namespaces + using namespace Charcode; + using namespace Character; + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + StringVector KeyframeSelectorParser::parse() { + + StringVector selectors; + do { + scanWhitespace(); + if (lookingAtIdentifier()) { + if (scanIdentifier("from")) { + selectors.emplace_back("from"); + } + else { + expectIdentifier("to", + "\"to\" or \"from\""); + selectors.emplace_back("to"); + } + } + else { + selectors.emplace_back(readPercentage()); + } + scanWhitespace(); + } + while (scanner.scanChar($comma)); + + scanner.expectDone(); + return selectors; + } + + sass::string KeyframeSelectorParser::readPercentage() + { + + StringBuffer buffer; + if (scanner.scanChar($plus)) { + buffer.writeCharCode($plus); + } + + uint8_t second = scanner.peekChar(); + if (!isDigit(second) && second != $dot) { + error("Expected number.", + scanner.rawSpan()); + } + + while (isDigit(scanner.peekChar())) { + buffer.writeCharCode(scanner.readChar()); + } + + if (scanner.peekChar() == $dot) { + buffer.writeCharCode(scanner.readChar()); + + while (isDigit(scanner.peekChar())) { + buffer.writeCharCode(scanner.readChar()); + } + } + + if (scanIdentCharInsensitive($e)) { + buffer.writeCharCode($e); + uint8_t next = scanner.peekChar(); + if (next == $plus || next == $minus) buffer.write(scanner.readChar()); + if (!isDigit(scanner.peekChar())) { + error("Expected digit.", + scanner.rawSpan()); + } + + while (isDigit(scanner.peekChar())) { + buffer.writeCharCode(scanner.readChar()); + } + } + + scanner.expectChar($percent); + buffer.writeCharCode($percent); + return buffer.buffer; + + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + +} diff --git a/src/parser_keyframe_selector.hpp b/src/parser_keyframe_selector.hpp new file mode 100644 index 0000000000..cc3daa3e39 --- /dev/null +++ b/src/parser_keyframe_selector.hpp @@ -0,0 +1,40 @@ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#ifndef SASS_PARSER_KEYFRAME_SELECTOR_HPP +#define SASS_PARSER_KEYFRAME_SELECTOR_HPP + +// sass.hpp must go before all system headers +// to get the __EXTENSIONS__ fix on Solaris. +#include "capi_sass.hpp" + +#include "parser.hpp" + +namespace Sass { + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // A parser for `@keyframes` block selectors. + class KeyframeSelectorParser final : public Parser + { + public: + + KeyframeSelectorParser( + Compiler& context, + SourceDataObj source) : + Parser(context, source) + {} + + StringVector parse(); + + sass::string readPercentage(); + + }; + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + +} + +#endif diff --git a/src/parser_media_query.cpp b/src/parser_media_query.cpp new file mode 100644 index 0000000000..71e9bae247 --- /dev/null +++ b/src/parser_media_query.cpp @@ -0,0 +1,235 @@ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +/* Implementations are mostly a direct code-port from dart-sass. */ +/*****************************************************************************/ +#include "parser_media_query.hpp" + +#include "ast_css.hpp" +#include "charcode.hpp" +#include "character.hpp" + +namespace Sass { + + // Import some namespaces + using namespace Charcode; + using namespace Character; + + // Consume multiple media queries delimited by commas. + CssMediaQueryVector MediaQueryParser::parse() + { + CssMediaQueryVector queries; + do { + scanWhitespace(); + queries.emplace_back(readMediaQuery()); + } while (scanner.scanChar($comma)); + scanner.expectDone(); + return queries; + } + + sass::string MediaQueryParser::readMediaInParens() { + scanner.expectChar($lparen, "media condition in parentheses"); + sass::string result = "(" + declarationValue() +")"; + scanner.expectChar($rparen); + return result; + } + + + /// Consumes one or more `` expressions separated by + /// [operator] and returns them. + sass::vector MediaQueryParser::readMediaLogicSequence2(sass::string op) { + sass::vector result; + while (true) { + result.push_back(readMediaInParens()); + scanWhitespace(); + if (!scanIdentifier(op)) return result; + expectWhitespace(); + } + } + + + // Consumes a single media query. + CssMediaQuery* MediaQueryParser::readMediaQuery() + { + + // This is somewhat duplicated in StylesheetParser.readMediaQuery. + + Offset start(scanner.offset); + + if (scanner.peekChar() == $lparen) { + StringVector conditions; + conditions.push_back(readMediaInParens()); + scanWhitespace(); + + bool conjunction = true; + if (scanIdentifier("and")) { + expectWhitespace(); + auto ands = readMediaLogicSequence2("and"); + std::copy(std::begin(ands), std::end(ands), + std::back_inserter(conditions)); + } + else if (scanIdentifier("or")) { + expectWhitespace(); + conjunction = false; + auto ors = readMediaLogicSequence2("or"); + std::copy(std::begin(ors), std::end(ors), + std::back_inserter(conditions)); + } + + return SASS_MEMORY_NEW(CssMediaQuery, + scanner.rawSpanFrom(start), + std::move(conditions), + conjunction); + } + + + sass::string type; + sass::string modifier; + + auto identifier1 = readIdentifier(); + if (StringUtils::equalsIgnoreCase(identifier1, "not", 3)) { + expectWhitespace(); + if (!lookingAtIdentifier()) { + // For example, "@media not (...) {" + // For example, "@media screen {" + + // return CssMediaQuery.condition(["(not ${_mediaInParens()})"]); + sass::string str("(not " + readMediaInParens() + ")"); + return SASS_MEMORY_NEW(CssMediaQuery, + scanner.rawSpanFrom(start), { str }); + } + } + + scanWhitespace(); + if (!lookingAtIdentifier()) { + // For example, "@media screen {" + // return CssMediaQuery.type(identifier1); + return SASS_MEMORY_NEW(CssMediaQuery, + scanner.rawSpanFrom(start), + std::move(identifier1)); + } + + auto identifier2 = readIdentifier(); + + if (StringUtils::equalsIgnoreCase(identifier2, "and", 3)) { + expectWhitespace(); + // For example, "@media screen and ..." + type = identifier1; + } + else { + scanWhitespace(); + modifier = identifier1; + type = identifier2; + if (scanIdentifier("and")) { + // For example, "@media only screen and ..." + expectWhitespace(); + } + else { + // For example, "@media only screen {" + // return CssMediaQuery.type(type, modifier: modifier); + return SASS_MEMORY_NEW(CssMediaQuery, + scanner.rawSpanFrom(start), + std::move(type), + std::move(modifier)); + + } + } + + // We've consumed either `IDENTIFIER "and"` or + // `IDENTIFIER IDENTIFIER "and"`. + + if (scanIdentifier("not")) { + // For example, "@media screen and not (...) {" + expectWhitespace(); + return SASS_MEMORY_NEW(CssMediaQuery, + scanner.rawSpanFrom(start), + std::move(type), std::move(modifier), + { "(not " + readMediaInParens() + ")" }); + + //return SASS_MEMORY_NEW(CssMediaQuery, + // scanner.rawSpanFrom(start), + // std::move(type), + // std::move(modifier)); + + // return CssMediaQuery.type(type, + // modifier: modifier, conditions : ["(not ${_mediaInParens()})"] ); + } + + auto qwe = SASS_MEMORY_NEW(CssMediaQuery, + scanner.rawSpanFrom(start), + std::move(type), std::move(modifier), + std::move(readMediaLogicSequence2("and"))); + + return qwe; + + //return CssMediaQuery.type(type, + // modifier: modifier, conditions : _mediaLogicSequence("and")); + +/* + // This is somewhat duplicated in StylesheetParser.readMediaQuery. + if (scanner.peekChar() != $lparen) { + sass::string identifier1 = readIdentifier(); + scanWhitespace(); + + if (!lookingAtIdentifier()) { + // For example, "@media screen {" + return SASS_MEMORY_NEW(CssMediaQuery, + scanner.rawSpanFrom(start), + std::move(identifier1)); + } + + sass::string identifier2 = readIdentifier(); + scanWhitespace(); + + if (StringUtils::equalsIgnoreCase(identifier2, "and", 3)) { + // For example, "@media screen and ..." + type = identifier1; + } + else { + modifier = identifier1; + type = identifier2; + if (scanIdentifier("and")) { + // For example, "@media only screen and ..." + scanWhitespace(); + } + else { + // For example, "@media only screen {" + return SASS_MEMORY_NEW(CssMediaQuery, + scanner.rawSpanFrom(start), + std::move(type), + std::move(modifier)); + } + } + } + + // We've consumed either `IDENTIFIER "and"`, `IDENTIFIER IDENTIFIER "and"`, + // or no text. + + StringVector features; + do { + scanWhitespace(); + scanner.expectChar($lparen); + auto decl = declarationValue(); + features.emplace_back("(" + decl + ")"); + scanner.expectChar($rparen); + scanWhitespace(); + } while (scanIdentifier("and")); + + if (type.empty()) { + return SASS_MEMORY_NEW(CssMediaQuery, + scanner.rawSpanFrom(start), + "", "", std::move(features)); + } + else { + return SASS_MEMORY_NEW(CssMediaQuery, + scanner.rawSpanFrom(start), + std::move(type), + std::move(modifier), + std::move(features)); + } + */ + + } + // EO readMediaQuery + +} diff --git a/src/parser_media_query.hpp b/src/parser_media_query.hpp new file mode 100644 index 0000000000..64e12cdc3c --- /dev/null +++ b/src/parser_media_query.hpp @@ -0,0 +1,48 @@ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#ifndef SASS_PARSER_MEDIA_QUERY_HPP +#define SASS_PARSER_MEDIA_QUERY_HPP + +// sass.hpp must go before all system headers +// to get the __EXTENSIONS__ fix on Solaris. +#include "capi_sass.hpp" + +#include "parser.hpp" + +namespace Sass { + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + class MediaQueryParser final : public Parser + { + public: + + // Value constructor + MediaQueryParser( + Compiler& context, + SourceDataObj source) : + Parser(context, source) + {} + + // Consume multiple media queries delimited by commas. + CssMediaQueryVector parse(); + + sass::string readMediaInParens(); + + private: + + sass::vector readMediaLogicSequence2(sass::string op); + + // Consumes a single media query. + CssMediaQuery* readMediaQuery(); + + }; + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + +} + +#endif diff --git a/src/parser_sass.cpp b/src/parser_sass.cpp new file mode 100644 index 0000000000..efbc7f990b --- /dev/null +++ b/src/parser_sass.cpp @@ -0,0 +1,535 @@ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +/* Implementations are mostly a direct code-port from dart-sass. */ +/*****************************************************************************/ +#include "parser_sass.hpp" + +#include "character.hpp" +#include "utf8/checked.h" +#include "interpolation.hpp" +#include "ast_imports.hpp" +#include "compiler.hpp" + +namespace Sass { + + // Import some namespaces + using namespace Charcode; + using namespace Character; + + Interpolation* SassParser::styleRuleSelector() + { + Offset start(scanner.offset); + InterpolationBuffer buffer(scanner); + do { + buffer.addInterpolation(readAlmostAnyValue(true)); + buffer.writeCharCode($lf); + } while (buffer.trailingStringEndsWith(",") && + scanCharIf(isNewline)); + + return buffer.getInterpolation(scanner.rawSpanFrom(start)); + } + + // Consumes and ignores a loud (CSS-style) comment. + // This overrides loud comment consumption so that + // it doesn't consume multi-line comments. + void SassParser::scanLoudComment() + { + scanner.expect("/*"); + while (true) { + auto next = scanner.readChar(); + if (isNewline(next)) { + scanner._fail("*/"); + } + if (next != $asterisk) continue; + + do { + next = scanner.readChar(); + } while (next == $asterisk); + if (next == $slash) break; + } + } + + void SassParser::expectStatementSeparator(sass::string name) { + if (!atEndOfStatement()) expectNewline(); + if (peekIndentation() <= currentIndentation) return; + sass::sstream strm; + strm << "Nothing may be indented "; + if (name.empty()) { strm << "here."; } + else { strm << "beneath a " + name + "."; } + Offset start(scanner.relevant); + while (uint32_t chr = scanner.peekChar()) { + if (!isWhitespace(chr)) break; + scanner.readChar(); + } + error(strm.str(), scanner.rawSpanFrom(start)); + + /*, + position: nextIndentationEnd.position*/ + } + + bool SassParser::atEndOfStatement() + { + uint8_t next; + if (scanner.peekChar(next)) { + return isNewline(next); + } + return true; + } + + bool SassParser::lookingAtChildren() + { + return atEndOfStatement() && + peekIndentation() > currentIndentation; + } + + void SassParser::scanImportArgument(ImportRule* rule) + { + uint8_t next = scanner.peekChar(); + StringScannerState state(scanner.state()); + switch (next) { + case $u: + case $U: + if (scanIdentifier("url")) { + if (scanner.scanChar($lparen)) { + scanner.backtrack(state); + StylesheetParser::scanImportArgument(rule); + return; + } + else { + scanner.backtrack(state); + } + } + break; + + case $quote: + case $apos: + StylesheetParser::scanImportArgument(rule); + return; + } + + Offset start(scanner.offset); + StringScannerState state2(scanner.state()); + while (scanner.peekChar(next) && + next != $comma && + next != $semicolon && + !isNewline(next)) { + scanner.readChar(); + next = scanner.peekChar(); + } + + sass::string url = scanner.substring(state2.position); + + if (isPlainImportUrl(url)) { + InterpolationObj itpl = SASS_MEMORY_NEW( + Interpolation, scanner.relevantSpanFrom(start)); + auto str = SASS_MEMORY_NEW(String, + scanner.relevantSpanFrom(start), + std::move(url), true); + // Must be an easier way to get quotes? + str->value(str->inspect()); + itpl->append(str); + rule->append(SASS_MEMORY_NEW(StaticImport, + scanner.relevantSpanFrom(start), itpl, nullptr)); + } + else { + + SourceSpan pstate(scanner.relevantSpanFrom(start)); + if (!compiler.callCustomImporters(url, pstate, rule)) { + rule->append(SASS_MEMORY_NEW(IncludeImport, + pstate, scanner.sourceUrl, url, nullptr)); + } + + } + + } + + bool SassParser::scanElse(size_t ifIndentation) + { + if (peekIndentation() != ifIndentation) return false; + StringScannerState state(scanner.state()); + size_t startIndentation = currentIndentation; + size_t startNextIndentation = nextIndentation; + StringScannerState startNextIndentationEnd = nextIndentationEnd; + + readIndentation(); + if (scanner.scanChar($at) && scanIdentifier("else")) return true; + + scanner.backtrack(state); + currentIndentation = startIndentation; + nextIndentation = startNextIndentation; + nextIndentationEnd = startNextIndentationEnd; + return false; + } + + StatementVector SassParser::readChildren(Statement* (StylesheetParser::* parser)()) + { + StatementVector children; + whileIndentedLower(parser, children); + return children; + } + + StatementVector SassParser::readStatements(Statement* (StylesheetParser::* parser)()) + { + uint8_t first = scanner.peekChar(); + if (first == $tab || first == $space) { + error("Indenting at the beginning of the document is illegal.", + scanner.rawSpan()); + /*position: 0, length : scanner.position*/ + } + + StatementVector statements; + while (!scanner.isDone()) { + Statement* child = parseChild(parser); + if (child != nullptr) statements.emplace_back(child); + size_t indentation = readIndentation(); + if (indentation != 0) { + error( + "Inconsistent indentation, expected 0 spaces.", + scanner.rawSpan()); + } + } + return statements; + } + + Statement* SassParser::parseChild(Statement* (StylesheetParser::* child)()) + { + switch (scanner.peekChar()) { + // Ignore empty lines. + case $cr: + case $lf: + case $ff: + return nullptr; + + case $dollar: + return readVariableDeclarationWithoutNamespace("", scanner.offset); + break; + + case $slash: + switch (scanner.peekChar(1)) { + case $slash: + lastSilentComment = readSilentComment(); + return lastSilentComment.ptr(); + break; + case $asterisk: + return readLoudComment(); + break; + default: + return (this->*child)(); + break; + } + break; + + default: + return (this->*child)(); + break; + } + } + + SilentComment* SassParser::readSilentComment() + { + Offset start(scanner.offset); + scanner.expect("//"); + StringBuffer buffer; + size_t parentIndentation = currentIndentation; + + do { + sass::string commentPrefix = scanner.scanChar($slash) ? "///" : "//"; + + while (true) { + buffer.write(commentPrefix); + + // Skip the initial characters because we're already writing the + // slashes. + for (size_t i = commentPrefix.length(); + i < currentIndentation - parentIndentation; + i++) { + buffer.writeCharCode($space); + } + + while (!scanner.isDone() && !isNewline(scanner.peekChar())) { + buffer.writeCharCode(scanner.readChar()); + } + buffer.write("\n"); + + if (peekIndentation() < parentIndentation) goto endOfLoop; + + if (peekIndentation() == parentIndentation) { + // Look ahead to the next line to see if it starts another comment. + if (scanner.peekChar(1 + parentIndentation) == $slash && + scanner.peekChar(2 + parentIndentation) == $slash) { + readIndentation(); + } + break; + } + readIndentation(); + } + } while (scanner.scan("//")); + + endOfLoop: + + lastSilentComment = SASS_MEMORY_NEW(SilentComment, + scanner.rawSpanFrom(start), std::move(buffer.buffer)); + return lastSilentComment; + } + + LoudComment* SassParser::readLoudComment() + { + Offset start(scanner.offset); + scanner.expect("/*"); + + bool first = true; + InterpolationBuffer buffer(scanner); + buffer.write("/*"); + size_t parentIndentation = currentIndentation; + while (true) { + if (first) { + // If the first line is empty, ignore it. + const char* beginningOfComment = scanner.position; + scanSpaces(); + if (isNewline(scanner.peekChar())) { + readIndentation(); + buffer.writeCharCode($space); + } + else { + buffer.write(scanner.substring(beginningOfComment)); + } + } + else { + buffer.write("\n"); + buffer.write(" * "); + } + first = false; + + for (size_t i = 3; i < currentIndentation - parentIndentation; i++) { + buffer.writeCharCode($space); + } + + while (!scanner.isDone()) { + uint8_t next = scanner.peekChar(); + switch (next) { + case $lf: + case $cr: + case $ff: + goto endOfLoop; + + case $hash: + if (scanner.peekChar(1) == $lbrace) { + buffer.add(readSingleInterpolation()); + } + else { + buffer.writeCharCode(scanner.readChar()); + } + break; + + default: + buffer.writeCharCode(scanner.readChar()); + break; + } + } + + endOfLoop: + + if (peekIndentation() <= parentIndentation) break; + + // Preserve empty lines. + while (lookingAtDoubleNewline()) { + expectNewline(); + buffer.write("\n"); + buffer.write(" *"); + } + + readIndentation(); + } + + if (!buffer.trailingStringEndsWith("*/")) { + buffer.write(" */"); + } + + SourceSpan pstate(scanner.rawSpanFrom(start)); + InterpolationObj itpl = buffer.getInterpolation(pstate); + return SASS_MEMORY_NEW(LoudComment, std::move(pstate), itpl); + } + + void SassParser::scanWhitespaceWithoutComments() + { + // This overrides whitespace consumption so that + // it doesn't consume newlines or loud comments. + while (!scanner.isDone()) { + uint8_t next = scanner.peekChar(); + if (next != $tab && next != $space) break; + scanner.readChar(); + } + + if (scanner.peekChar() == $slash && scanner.peekChar(1) == $slash) { + lastSilentComment = readSilentComment(); + } + } + + void SassParser::expectNewline() + { + switch (scanner.peekChar()) { + case $semicolon: + error("semicolons aren't allowed in the indented syntax.", + scanner.rawSpan()); + return; + case $cr: + scanner.readChar(); + if (scanner.peekChar() == $lf) scanner.readChar(); + return; + case $lf: + case $ff: + scanner.readChar(); + return; + default: + error("expected newline.", + scanner.rawSpan()); + } + } + + bool SassParser::lookingAtDoubleNewline() + { + uint8_t next = scanner.peekChar(); + uint8_t nextChar = scanner.peekChar(1); + switch (next) { + case $cr: + if (nextChar == $lf) return isNewline(scanner.peekChar(2)); + return nextChar == $cr || nextChar == $ff; + case $lf: + case $ff: + return isNewline(scanner.peekChar(1)); + default: + return false; + } + } + + void SassParser::whileIndentedLower(Statement* (StylesheetParser::* child)(), StatementVector& children) + { + size_t parentIndentation = currentIndentation; + size_t childIndentation = sass::string::npos; + while (peekIndentation() > parentIndentation) { + size_t indentation = readIndentation(); + if (childIndentation == sass::string::npos) { + childIndentation = indentation; + } + if (childIndentation != indentation) { + sass::sstream msg; + msg << "Inconsistent indentation, expected " + << childIndentation << " spaces."; + error(msg.str(), + scanner.rawSpan()); + + /*, + position: scanner.position - scanner.column, + length : scanner.column*/ + } + children.emplace_back(parseChild(child)); + } + + } + + size_t SassParser::readIndentation() + { + if (nextIndentation == sass::string::npos) { + peekIndentation(); + } + currentIndentation = nextIndentation; + scanner.backtrack(nextIndentationEnd); + nextIndentation = sass::string::npos; + // What does this mean, where is it used? + // nextIndentationEnd = null; ToDo + return currentIndentation; + + } + + // Returns the indentation level of the next line. + size_t SassParser::peekIndentation() + { + if (nextIndentation != sass::string::npos) { + return nextIndentation; + } + + if (scanner.isDone()) { + nextIndentation = 0; + nextIndentationEnd = scanner.state(); + return 0; + } + + StringScannerState start = scanner.state(); + if (!scanCharIf(isNewline)) { + error("Expected newline.", + scanner.rawSpan()); + /* position: scanner.position*/ + } + + bool containsTab; + bool containsSpace; + do { + containsTab = false; + containsSpace = false; + nextIndentation = 0; + + while (true) { + uint8_t next = scanner.peekChar(); + if (next == $space) { + containsSpace = true; + } + else if (next == $tab) { + containsTab = true; + } + else { + break; + } + nextIndentation++; + scanner.readChar(); + } + + if (scanner.isDone()) { + nextIndentation = 0; + nextIndentationEnd = scanner.state(); + scanner.backtrack(start); + return 0; + } + } while (scanCharIf(isNewline)); + + checkIndentationConsistency(containsTab, containsSpace); + + if (nextIndentation > 0) { + if (indentType == SassIndentType::AUTO) { + indentType = containsSpace ? + SassIndentType::SPACES : + SassIndentType::TABS; + } + } + nextIndentationEnd = scanner.state(); + scanner.backtrack(start); + return nextIndentation; + } + + // Ensures that the document uses consistent characters for indentation. + // The [containsTab] and [containsSpace] parameters refer to + // a single line of indentation that has just been parsed. + void SassParser::checkIndentationConsistency(bool containsTab, bool containsSpace) + { + if (containsTab) { + if (containsSpace) { + error("Tabs and spaces may not be mixed.", + scanner.rawSpan()); + /* position: scanner.position - scanner.column, + length : scanner.column*/ + } + else if (useSpaceIndentation()) { + error("Expected spaces, was tabs.", + scanner.rawSpan()); + /* position: scanner.position - scanner.column, + length : scanner.column*/ + } + } + else if (containsSpace && useTabIndentation()) { + error("Expected tabs, was spaces.", + scanner.rawSpan()); + /* position: scanner.position - scanner.column, length : scanner.column*/ + } + } + +} diff --git a/src/parser_sass.hpp b/src/parser_sass.hpp new file mode 100644 index 0000000000..7697305818 --- /dev/null +++ b/src/parser_sass.hpp @@ -0,0 +1,155 @@ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#ifndef SASS_PARSER_SASS_HPP +#define SASS_PARSER_SASS_HPP + +// sass.hpp must go before all system headers +// to get the __EXTENSIONS__ fix on Solaris. +#include "capi_sass.hpp" + +#include "parser_stylesheet.hpp" + +namespace Sass { + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + class SassParser final : public StylesheetParser + { + public: + + enum SassIndentType { + AUTO, TABS, SPACES, + }; + + SassParser( + Compiler& context, + SourceDataObj source) : + StylesheetParser( + context, source), + currentIndentation(0), + nextIndentation(NPOS), + nextIndentationEnd({ + source->content(), + Offset() }), + indentType(SassIndentType::AUTO) + {} + + protected: + + // The current indentation level + size_t currentIndentation = 0; + + // The indentation level of the next source line after the scanner's + // position, or `null` if that hasn't been computed yet. + // A source line is any line that's not entirely whitespace. + size_t nextIndentation; + + // The beginning of the next source line after the scanner's + // position, or `null` if that hasn't been computed yet. + // A source line is any line that's not entirely whitespace. + StringScannerState nextIndentationEnd; + + // Whether the document is indented using spaces or tabs. + // If this is `true`, the document is indented using spaces. If it's `false`, + // the document is indented using tabs. If it's `null`, we haven't yet seen + // the indentation character used by the document. + SassIndentType indentType = SassIndentType::AUTO; + + // Some helper function to do the most generic queries + bool useTabIndentation() { return indentType == SassIndentType::TABS; } + bool useSpaceIndentation() { return indentType == SassIndentType::SPACES; } + + // Whether this is a plain CSS stylesheet. + bool plainCss() const override final { return false; } + + // Whether this is parsing the indented syntax. + bool isIndented() const override final { return true; }; + + // Parses and returns a selector used in a style rule. + Interpolation* styleRuleSelector() override final; + + // Asserts that the scanner is positioned before a statement separator, + // or at the end of a list of statements. If the [name] of the parent + // rule is passed, it's used for error reporting. This consumes + // whitespace, but nothing else, including comments. + void expectStatementSeparator(sass::string name) override final; + + // Whether the scanner is positioned at the end of a statement. + bool atEndOfStatement() override final; + + // Whether the scanner is positioned before a block of + // children that can be parsed with [children]. + bool lookingAtChildren() override final; + + // Consumes an argument to an `@import` rule. + // If anything is found it will be added to [rule]. + void scanImportArgument(ImportRule* rule) override final; + + // Tries to scan an `@else` rule after an `@if` block. Returns whether + // that succeeded. This should just scan the rule name, not anything + // afterwards. [ifIndentation] is the result of [currentIndentation] + // from before the corresponding `@if` was parsed. + bool scanElse(size_t ifIndentation) override final; + + // Consumes a block of child statements. Unlike most production consumers, + // this does *not* consume trailing whitespace. This is necessary to ensure + // that the source span for the parent rule doesn't cover whitespace after the rule. + StatementVector readChildren(Statement* (StylesheetParser::* child)()) override final; + + // Consumes top-level statements. The [statement] callback may return `nullptr`, + // indicating that a statement was consumed that shouldn't be added to the AST. + StatementVector readStatements(Statement* (StylesheetParser::* statement)()) override final; + + // Consumes a child of the current statement. This consumes + // children that are allowed at all levels of the document; + // the [child] parameter is called to consume any children + // that are specifically allowed in the caller's context. + Statement* parseChild(Statement* (StylesheetParser::* statement)()); + + // Consumes an indented-style silent comment. + SilentComment* readSilentComment(); + + // Consumes an indented-style loud context. + // This overrides loud comment consumption so + // that it doesn't consume multi-line comments. + LoudComment* readLoudComment(); + + // Consumes and ignores a loud (CSS-style) comment. + // This overrides loud comment consumption so that + // it doesn't consume multi-line comments. + void scanLoudComment() override final; + + void scanWhitespaceWithoutComments() override final; + + // Expect and consume a single newline character. + void expectNewline(); + + // Returns whether the scanner is immediately before *two* newlines. + bool lookingAtDoubleNewline(); + + // As long as the scanner's position is indented beneath the + // starting line, runs [body] to consume the next statement. + void whileIndentedLower(Statement* (StylesheetParser::* child)(), StatementVector& children); + + // Consumes indentation whitespace and returns + // the indentation level of the next line. + size_t readIndentation(); + + // Returns the indentation level of the next line. + size_t peekIndentation(); + + // Ensures that the document uses consistent characters for indentation. + // The [containsTab] and [containsSpace] parameters refer to + // a single line of indentation that has just been parsed. + void checkIndentationConsistency(bool containsTab, bool containsSpace); + + }; + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + +} + +#endif diff --git a/src/parser_scss.cpp b/src/parser_scss.cpp new file mode 100644 index 0000000000..4aadca9c75 --- /dev/null +++ b/src/parser_scss.cpp @@ -0,0 +1,261 @@ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +/* Implementations are mostly a direct code-port from dart-sass. */ +/*****************************************************************************/ +#include "parser_scss.hpp" + +#include "character.hpp" +#include "utf8/checked.h" + +namespace Sass { + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // Import some namespaces + using namespace Charcode; + using namespace Character; + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // Parses and returns a selector used in a style rule. + Interpolation* ScssParser::styleRuleSelector() + { + return readAlmostAnyValue(); + } + // EO styleRuleSelector + + // Asserts that the scanner is positioned before a statement separator, + // or at the end of a list of statements. If the [name] of the parent + // rule is passed, it's used for error reporting. This consumes + // whitespace, but nothing else, including comments. + void ScssParser::expectStatementSeparator(sass::string name) + { + scanWhitespaceWithoutComments(); + if (scanner.isDone()) return; + uint8_t next = scanner.peekChar(); + if (next == $semicolon || next == $rbrace) return; + scanner.expectChar($semicolon); + } + // EO expectStatementSeparator + + // Whether the scanner is positioned at the end of a statement. + bool ScssParser::atEndOfStatement() + { + if (scanner.isDone()) return true; + uint8_t next = scanner.peekChar(); + return next == $semicolon + || next == $rbrace + || next == $lbrace; + } + // EO atEndOfStatement + + // Whether the scanner is positioned before a block of + // children that can be parsed with [children]. + bool ScssParser::lookingAtChildren() + { + return scanner.peekChar() == $lbrace; + } + // EO lookingAtChildren + + // Tries to scan an `@else` rule after an `@if` block, and returns whether + // that succeeded. This should just scan the rule name, not anything + // afterwards. [ifIndentation] is the result of [currentIndentation] + // from before the corresponding `@if` was parsed. + bool ScssParser::scanElse(size_t) + { + StringScannerState start = scanner.state(); + scanWhitespace(); + // StringScannerState beforeAt = scanner.state(); + if (scanner.scanChar($at)) { + if (scanIdentifier("else", true)) return true; + if (scanIdentifier("elseif", true)) { + /* + logger.warn( + "@elseif is deprecated and will not be supported in future Sass versions.\n" + "Use "@else if" instead.", + span: scanner.spanFrom(beforeAt), + deprecation : true); + */ + scanner.offset.column -= 2; + scanner.position -= 2; + return true; + } + } + scanner.backtrack(start); + return false; + } + // EO scanElse + + // Consumes a block of child statements. Unlike most production consumers, + // this does *not* consume trailing whitespace. This is necessary to ensure + // that the source span for the parent rule doesn't cover whitespace after the rule. + StatementVector ScssParser::readChildren( + Statement* (StylesheetParser::* child)()) + { + scanner.expectChar($lbrace); + scanWhitespaceWithoutComments(); + StatementVector children; + while (true) { + switch (scanner.peekChar()) { + case $dollar: + children.emplace_back(readVariableDeclarationWithoutNamespace("", scanner.offset)); + break; + + case $slash: + switch (scanner.peekChar(1)) { + case $slash: + lastSilentComment = readSilentComment(); + scanWhitespaceWithoutComments(); + break; + case $asterisk: + children.emplace_back(readLoudComment()); + scanWhitespaceWithoutComments(); + break; + default: + children.emplace_back((this->*child)()); + break; + } + break; + + case $semicolon: + scanner.readChar(); + scanWhitespaceWithoutComments(); + break; + + case $rbrace: + scanner.expectChar($rbrace); + return children; + + default: + children.emplace_back((this->*child)()); + break; + } + } + } + // EO children + + // Consumes top-level statements. The [statement] callback may return `null`, + // indicating that a statement was consumed that shouldn't be added to the AST. + StatementVector ScssParser::readStatements( + Statement* (StylesheetParser::* statement)()) + { + scanWhitespaceWithoutComments(); + StatementVector statements; + while (!scanner.isDone()) { + switch (scanner.peekChar()) { + case $dollar: + statements.emplace_back(readVariableDeclarationWithoutNamespace("", scanner.offset)); + break; + + case $slash: + switch (scanner.peekChar(1)) { + case $slash: + lastSilentComment = readSilentComment(); + scanWhitespaceWithoutComments(); + break; + case $asterisk: + statements.emplace_back(readLoudComment()); + scanWhitespaceWithoutComments(); + break; + default: + StatementObj child = (this->*statement)(); + if (child != nullptr) statements.emplace_back(child); + break; + } + break; + + case $semicolon: + scanner.readChar(); + scanWhitespaceWithoutComments(); + break; + + default: + StatementObj child = (this->*statement)(); + if (child != nullptr) statements.emplace_back(child); + break; + } + } + return statements; + } + // EO statements + + // Consumes a statement-level silent comment block. + SilentComment* ScssParser::readSilentComment() + { + StringScannerState start = scanner.state(); + scanner.expect("//"); + + do { + while (!scanner.isDone() && + !isNewline(scanner.readChar())) {} + if (scanner.isDone()) break; + scanWhitespaceWithoutComments(); + } + while (scanner.scan("//")); + + if (plainCss()) { + error("Silent comments aren't allowed in plain CSS.", + scanner.relevantSpanFrom(start.offset)); + } + + return SASS_MEMORY_NEW(SilentComment, scanner.rawSpanFrom(start.offset), + scanner.substring(start.position, scanner.position)); + } + // EO readSilentComment + + // Consumes a statement-level loud comment block. + LoudComment* ScssParser::readLoudComment() + { + InterpolationBuffer buffer(scanner); + Offset start(scanner.offset); + scanner.expect("/*"); + buffer.write("/*"); + while (true) { + switch (scanner.peekChar()) { + case $hash: + if (scanner.peekChar(1) == $lbrace) { + buffer.add(readSingleInterpolation()); + } + else { + buffer.write(scanner.readChar()); + } + break; + + case $asterisk: + buffer.write(scanner.readChar()); + if (scanner.peekChar() != $slash) break; + buffer.write(scanner.readChar()); + return SASS_MEMORY_NEW(LoudComment, + scanner.rawSpanFrom(start), + buffer.getInterpolation( + scanner.rawSpanFrom(start))); + + case $cr: + scanner.readChar(); + if (scanner.peekChar() != $lf) { + buffer.write($lf); + } + break; + + case $ff: + scanner.readChar(); + buffer.write($lf); + break; + + default: + buffer.write(scanner.readChar()); + break; + } + } + + return nullptr; + } + // EO loudComment + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + +} diff --git a/src/parser_scss.hpp b/src/parser_scss.hpp new file mode 100644 index 0000000000..33a66df292 --- /dev/null +++ b/src/parser_scss.hpp @@ -0,0 +1,93 @@ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#ifndef SASS_PARSER_SCSS_HPP +#define SASS_PARSER_SCSS_HPP + +// sass.hpp must go before all system headers +// to get the __EXTENSIONS__ fix on Solaris. +#include "capi_sass.hpp" + +#include "parser_stylesheet.hpp" + +namespace Sass { + + class ScssParser : public StylesheetParser + { + public: + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + ScssParser( + Compiler& context, + SourceDataObj source) : + StylesheetParser( + context, source) + {} + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // So far we are not parsing css + virtual bool plainCss() const override { + return false; + } + + // We are sure scss is not indented syntax + bool isIndented() const override final { + return false; + }; + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + protected: + + // Parses and returns a selector used in a style rule. + virtual Interpolation* styleRuleSelector() override; + + // Asserts that the scanner is positioned before a statement separator, + // or at the end of a list of statements. If the [name] of the parent + // rule is passed, it's used for error reporting. This consumes + // whitespace, but nothing else, including comments. + virtual void expectStatementSeparator(sass::string name) override; + + // Whether the scanner is positioned at the end of a statement. + virtual bool atEndOfStatement() override; + + // Whether the scanner is positioned before a block of + // children that can be parsed with [children]. + virtual bool lookingAtChildren() override; + + // Tries to scan an `@else` rule after an `@if` block, and returns whether + // that succeeded. This should just scan the rule name, not anything + // afterwards. [ifIndentation] is the result of [currentIndentation] + // from before the corresponding `@if` was parsed. + virtual bool scanElse(size_t ifIndentation) override; + + // Consumes a block of child statements. Unlike most production consumers, + // this does *not* consume trailing whitespace. This is necessary to ensure + // that the source span for the parent rule doesn't cover whitespace after the rule. + virtual StatementVector readChildren( + Statement* (StylesheetParser::* child)()) override; + + // Consumes top-level statements. The [statement] callback may return `null`, + // indicating that a statement was consumed that shouldn't be added to the AST. + virtual StatementVector readStatements( + Statement* (StylesheetParser::* statement)()) override; + + // Consumes a statement-level silent comment block. + virtual SilentComment* readSilentComment(); + + // Consumes a statement-level loud comment block. + virtual LoudComment* readLoudComment(); + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + }; + +} + +#endif diff --git a/src/parser_selector.cpp b/src/parser_selector.cpp new file mode 100644 index 0000000000..9dcf84d53f --- /dev/null +++ b/src/parser_selector.cpp @@ -0,0 +1,614 @@ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +/* Implementations are mostly a direct code-port from dart-sass. */ +/*****************************************************************************/ +#include "parser_selector.hpp" + +#include "charcode.hpp" +#include "character.hpp" +#include "ast_selectors.hpp" +#include "scanner_string.hpp" + +#include "debugger.hpp" + +namespace Sass { + + // Import some namespaces + using namespace Charcode; + using namespace Character; + + // Parse content into selector list + // Throws if not everything is consumed + SelectorList* SelectorParser::parseSelectorList() + { + SelectorListObj selector(readSelectorList()); + if (!scanner.isDone()) { + error("expected selector.", + scanner.rawSpan()); + } + return selector.detach(); + } + // EO parseSelectorList + + // Parse content into compound selector + // Throws if not everything is consumed + CompoundSelector* SelectorParser::parseCompoundSelector() + { + CompoundSelectorObj compound(readCompoundSelector()); + if (!scanner.isDone()) { + error("expected selector.", + scanner.rawSpan()); + } + return compound.detach(); + } + // EO parseCompoundSelector + + // Parse content into simple selector + // Throws if not everything is consumed + SimpleSelector* SelectorParser::parseSimpleSelector() + { + SimpleSelectorObj simple(readSimpleSelector(allowParent)); + if (!scanner.isDone()) { + error("unexpected token.", + scanner.relevantSpan()); + } + return simple.detach(); + } + // EO parseSimpleSelector + + // Consumes a selector list. + SelectorList* SelectorParser::readSelectorList() + { + Offset start(scanner.offset); + const char* previousLine = scanner.position; + sass::vector items; + items.emplace_back(readComplexSelector()); + + scanWhitespace(); + while (scanner.scanChar($comma)) { + scanWhitespace(); + uint8_t next = scanner.peekChar(); + if (next == $comma) continue; + if (scanner.isDone()) break; + + bool lineBreak = scanner.hasLineBreak(previousLine); // ToDo + //bool lineBreak = scanner.position != previousLine; + //if (lineBreak) previousLine = scanner.position; + // std::cerr << "With line break " << lineBreak << "\n"; + auto sel = readComplexSelector(lineBreak); + items.emplace_back(sel); + } + + return SASS_MEMORY_NEW(SelectorList, + scanner.relevantSpanFrom(start), std::move(items)); + } + // EO readSelectorList + + // Consumes a complex selector. + ComplexSelector* SelectorParser::readComplexSelector(bool lineBreak) + { + + uint8_t next; + + Offset start(scanner.offset); + Offset offset(scanner.offset); + Offset pcomb(scanner.offset); + // CplxSelComponentVector complex; + + CompoundSelectorObj lastCompound; + CplxSelComponentObj lastComponent; + + CplxSelComponentVector components; + SelectorCombinatorVector combinators; + SelectorCombinatorVector prefixes; + + while (true) { + scanWhitespace(); + + Offset before(scanner.offset); + bool hasIdentifier = false; + if (!scanner.peekChar(next)) { + goto endOfLoop; + } + switch (next) { + case $plus: + pcomb = scanner.offset; + scanner.readChar(); + combinators.emplace_back(SASS_MEMORY_NEW(SelectorCombinator, + scanner.rawSpanFrom(pcomb), SelectorPrefix::SIBLING)); + break; + + case $gt: + pcomb = scanner.offset; + scanner.readChar(); + combinators.emplace_back(SASS_MEMORY_NEW(SelectorCombinator, + scanner.rawSpanFrom(pcomb), SelectorPrefix::CHILD)); + break; + + case $tilde: + pcomb = scanner.offset; + scanner.readChar(); + combinators.emplace_back(SASS_MEMORY_NEW(SelectorCombinator, + scanner.rawSpanFrom(pcomb), SelectorPrefix::FOLLOWING)); + break; + + // Found a component? + case $lbracket: + case $dot: + case $hash: + case $percent: + case $colon: + case $ampersand: + case $asterisk: + case $pipe: + hasIdentifier = true; + /* FALLTHRU */ + default: + if (hasIdentifier || lookingAtIdentifier()) + { + if (lastCompound != nullptr) { + components.push_back(SASS_MEMORY_NEW(CplxSelComponent, + scanner.rawSpanFrom(offset), // from inner offset + std::move(combinators), // add postfix combinators + lastCompound)); // use previously parsed compound + } + else if (!combinators.empty()) { + prefixes = std::move(combinators); + offset = scanner.offset; // reset + } + lastCompound = readCompoundSelector(); + combinators.clear(); // restart them + if (scanner.peekChar() == $ampersand) { + error( + "\"&\" may only used at the beginning of a compound selector.", + scanner.rawSpan()); + } + } + else { + goto endOfLoop; + } + hasIdentifier = false; + break; + + } + } + + endOfLoop: + + if (lastCompound != nullptr) { + components.push_back(SASS_MEMORY_NEW(CplxSelComponent, + scanner.rawSpanFrom(offset), // from inner offset + std::move(combinators), // add postfix combinators + lastCompound)); // use previously parsed compound + } + else if (!combinators.empty()) { + prefixes = std::move(combinators); + } + else if (components.empty()) { + error("expected selector.", + scanner.rawSpan()); + } + + ComplexSelector* selector = SASS_MEMORY_NEW(ComplexSelector, + scanner.rawSpanFrom(start), + std::move(prefixes), + std::move(components)); + selector->hasPreLineFeed(lineBreak); + + // std::cerr << "parsing " << scanner.startpos << "\n"; + // std::cerr << "parsed result => " << selector->inspect() << "\n"; + + // debug_ast(selector); + + return selector; + + } + // EO readComplexSelector + + // Consumes a compound selector. + CompoundSelector* SelectorParser::readCompoundSelector() + { + // Note: libsass uses a flag on the compound selector to + // signal that it contains a real parent reference. + // dart-sass uses ParentSelector with a suffix. + Offset start(scanner.offset); + CompoundSelectorObj compound = SASS_MEMORY_NEW(CompoundSelector, + scanner.relevantSpan()); + + if (scanner.scanChar($ampersand)) { + if (!allowParent) { + error( + "Parent selectors aren't allowed here.", + scanner.rawSpanFrom(start)); + } + compound->withExplicitParent(true); + if (lookingAtIdentifierBody()) { + Offset before(scanner.offset); + sass::string body(identifierBody()); + SimpleSelectorObj simple = SASS_MEMORY_NEW(TypeSelector, + scanner.rawSpanFrom(before), std::move(body), "", false); + if (!simple.isNull()) compound->append(simple); + } + } + else { + SimpleSelectorObj simple = readSimpleSelector(false); + if (!simple.isNull()) compound->append(simple); + } + + while (isSimpleSelectorStart(scanner.peekChar())) { + SimpleSelectorObj simple = readSimpleSelector(false); + if (!simple.isNull()) compound->append(simple); + } + + compound->pstate(scanner.rawSpanFrom(start)); + return compound.detach(); + + } + // EO readCompoundSelector + + // Consumes a simple selector. + SimpleSelector* SelectorParser::readSimpleSelector(bool allowParent) + { + + Offset start(scanner.offset); + uint8_t next = scanner.peekChar(); + if (next == $lbracket) { + return readAttributeSelector(); + } + else if (next == $dot) { + return readClassSelector(); + } + else if (next == $hash) { + return readIdSelector(); + } + else if (next == $percent) + { + PlaceholderSelectorObj selector(readPlaceholderSelector()); + if (!allowPlaceholder) { + error("Placeholder selectors aren't allowed here.", + scanner.rawSpanFrom(start)); + } + return selector.detach(); + } + else if (next == $colon) { + return readPseudoSelector(); + } + else if (next == $ampersand) + { + if (!allowParent) { + error( + "Parent selectors aren't allowed here.", + scanner.rawSpanFrom(start)); + } + return {}; + } + else { + return readTypeOrUniversalSelector(); + } + + } + // EO readSimpleSelector + + // Consumes an attribute selector. + AttributeSelector* SelectorParser::readAttributeSelector() + { + + scanner.expectChar($lbracket); + + scanWhitespace(); + Offset start(scanner.offset); + struct QualifiedName name(readAttributeName()); + SourceSpan span(scanner.relevantSpanFrom(start)); + scanWhitespace(); + + if (scanner.scanChar($rbracket)) { + return SASS_MEMORY_NEW(AttributeSelector, + span, std::move(name)); + } + + sass::string op(readAttributeOperator()); + + scanWhitespace(); + + bool isIdent = true; + sass::string value; + uint8_t next = scanner.peekChar(); + // Check if we are looking at an unquoted text + if (next != $quote && next != $apos) { + value = readIdentifier(); + } + else { + value = string(); + isIdent = isIdentifier(value); + } + + scanWhitespace(); + uint8_t modifier = 0; + if (isAlphabetic(scanner.peekChar())) { + modifier = scanner.readChar(); + scanWhitespace(); + } + + span = scanner.relevantSpanFrom(start); + + scanner.expectChar($rbracket); + + return SASS_MEMORY_NEW(AttributeSelector, span, + std::move(name), std::move(op), + std::move(value), isIdent, + modifier); + + } + // EO readAttributeSelector + + // Consumes an attribute name. + struct QualifiedName SelectorParser::readAttributeName() + { + + if (scanner.scanChar($asterisk)) { + scanner.expectChar($pipe); + return { readIdentifier(), "*", true }; + } + + if (scanner.scanChar($pipe)) { + return { readIdentifier(), "", true }; + } + + sass::string nameOrNamespace = readIdentifier(); + if (scanner.peekChar() != $pipe || scanner.peekChar(1) == $equal) { + return { std::move(nameOrNamespace), "", false }; + } + + scanner.readChar(); + return { readIdentifier(), std::move(nameOrNamespace), true }; + + } + // EO readAttributeName + + // Consumes an attribute operator. + sass::string SelectorParser::readAttributeOperator() + { + Offset start(scanner.offset); + switch (scanner.readChar()) { + case $equal: + return "="; // AttributeOperator.equal; + + case $tilde: + scanner.expectChar($equal); + return "~="; // AttributeOperator.include; + + case $pipe: + scanner.expectChar($equal); + return "|="; // AttributeOperator.dash; + + case $caret: + scanner.expectChar($equal); + return "^="; // AttributeOperator.prefix; + + case $dollar: + scanner.expectChar($equal); + return "$="; // AttributeOperator.suffix; + + case $asterisk: + scanner.expectChar($equal); + return "*="; // AttributeOperator.substring; + + default: + error("Expected \"]\".", + scanner.rawSpanFrom(start)); + throw "Unreachable"; + } + } + // EO readAttributeOperator + + // Consumes a class operator. + ClassSelector* SelectorParser::readClassSelector() + { + Offset start(scanner.offset); + scanner.expectChar($dot); + sass::string name = readIdentifier(); + return SASS_MEMORY_NEW(ClassSelector, + scanner.rawSpanFrom(start), "." + name); + } + // EO readClassSelector + + // Consumes an in operator. + IDSelector* SelectorParser::readIdSelector() + { + Offset start(scanner.offset); + scanner.expectChar($hash); + sass::string name = readIdentifier(); + return SASS_MEMORY_NEW(IDSelector, + scanner.rawSpanFrom(start), "#" + name); + } + // EO readIdSelector + + // Consumes a placeholder operator. + PlaceholderSelector* SelectorParser::readPlaceholderSelector() + { + Offset start(scanner.offset); + scanner.expectChar($percent); + sass::string name = readIdentifier(); + return SASS_MEMORY_NEW(PlaceholderSelector, + scanner.rawSpanFrom(start), "%" + name); + } + // EO readPlaceholderSelector + + // Consumes a pseudo operator. + PseudoSelector* SelectorParser::readPseudoSelector() + { + Offset start(scanner.offset); + scanner.expectChar($colon); + bool element = scanner.scanChar($colon); + sass::string name = readIdentifier(); + + if (!scanner.scanChar($lparen)) { + return SASS_MEMORY_NEW(PseudoSelector, + scanner.rawSpanFrom(start), name, element); + } + scanWhitespace(); + + sass::string unvendored(name); + unvendored = StringUtils::unvendor(unvendored); + + sass::string argument; + // Offset beforeArgument(scanner.offset); + SelectorListObj selector = SASS_MEMORY_NEW(SelectorList, scanner.relevantSpan()); + if (element) { + if (isSelectorPseudoElement(unvendored)) { + selector = readSelectorList(); + for (auto complex : selector->elements()) { + complex->chroots(true); + } + } + else { + argument = declarationValue(true); + } + } + else if (isSelectorPseudoClass(unvendored)) { + RAII_FLAG(allowParent, true); + selector = readSelectorList(); + for (auto complex : selector->elements()) { + complex->chroots(true); + } + } + else if (unvendored == "nth-child" || unvendored == "nth-last-child") { + argument = readAnPlusB(); + scanWhitespace(); + if (isWhitespace(scanner.peekChar(-1)) && scanner.peekChar() != $rparen) { + expectIdentifier("of", "\"of\""); + argument += " of"; + scanWhitespace(); + selector = readSelectorList(); + } + } + else { + argument = declarationValue(true); + StringUtils::makeRightTrimmed(argument); + } + scanner.expectChar($rparen); + + auto pseudo = SASS_MEMORY_NEW(PseudoSelector, + scanner.rawSpanFrom(start), + std::move(name), element != 0); + if (!selector->empty()) pseudo->selector(selector); + pseudo->argument(std::move(argument)); + return pseudo; + + } + // EO readPlaceholderSelector + + // Consumes an `an+b` expression. + sass::string SelectorParser::readAnPlusB() + { + + StringBuffer buffer; + uint8_t first, next, last; + switch (scanner.peekChar()) { + case $e: + case $E: + expectIdentifier("even", "\"even\""); + return "even"; + + case $o: + case $O: + expectIdentifier("odd", "\"odd\""); + return "odd"; + + case $plus: + case $minus: + buffer.write(scanner.readChar()); + break; + } + + if (scanner.peekChar(first) && isDigit(first)) { + while (isDigit(scanner.peekChar())) { + buffer.write(scanner.readChar()); + } + scanWhitespace(); + if (!scanIdentChar($n)) return buffer.buffer; + } + else { + expectIdentChar($n); + } + buffer.write($n); + scanWhitespace(); + + scanner.peekChar(next); + if (next != $plus && next != $minus) return buffer.buffer; + buffer.write(scanner.readChar()); + scanWhitespace(); + + if (!scanner.peekChar(last) || !isDigit(last)) { + error("Expected a number.", + scanner.rawSpan()); + } + while (isDigit(scanner.peekChar())) { + buffer.write(scanner.readChar()); + } + return buffer.buffer; + } + // readAnPlusB + + // Consumes a type of universal (simple) selector. + SimpleSelector* SelectorParser::readTypeOrUniversalSelector() + { + // Note: libsass has no explicit UniversalSelector, + // we use a regular type selector with name == "*". + Offset start(scanner.offset); + uint8_t first = scanner.peekChar(); + if (first == $asterisk) { + scanner.readChar(); + if (!scanner.scanChar($pipe)) { + return SASS_MEMORY_NEW(TypeSelector, + scanner.rawSpanFrom(start), + "*", "", false); + } + if (scanner.scanChar($asterisk)) { + return SASS_MEMORY_NEW(TypeSelector, + scanner.rawSpanFrom(start), + "*", "*", true); + } + else { + return SASS_MEMORY_NEW(TypeSelector, + scanner.rawSpanFrom(start), + readIdentifier(), "*", true); + } + } + else if (first == $pipe) { + scanner.readChar(); + if (scanner.scanChar($asterisk)) { + return SASS_MEMORY_NEW(TypeSelector, + scanner.rawSpanFrom(start), + "*", "", true); + } + else { + return SASS_MEMORY_NEW(TypeSelector, + scanner.rawSpanFrom(start), + readIdentifier(), "", true); + } + } + + sass::string nameOrNamespace = readIdentifier(); + if (!scanner.scanChar($pipe)) { + return SASS_MEMORY_NEW(TypeSelector, + scanner.rawSpanFrom(start), + std::move(nameOrNamespace), + "", false); + } + else if (scanner.scanChar($asterisk)) { + return SASS_MEMORY_NEW(TypeSelector, + scanner.rawSpanFrom(start), "*", + std::move(nameOrNamespace), true); + } + else { + return SASS_MEMORY_NEW(TypeSelector, + scanner.rawSpanFrom(start), readIdentifier(), + std::move(nameOrNamespace), true); + } + + } + // EO readTypeOrUniversalSelector + +} diff --git a/src/parser_selector.hpp b/src/parser_selector.hpp new file mode 100644 index 0000000000..9b14a8f699 --- /dev/null +++ b/src/parser_selector.hpp @@ -0,0 +1,99 @@ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#ifndef SASS_PARSER_SELECTOR_HPP +#define SASS_PARSER_SELECTOR_HPP + +// sass.hpp must go before all system headers +// to get the __EXTENSIONS__ fix on Solaris. +#include "capi_sass.hpp" + +#include "parser.hpp" + +namespace Sass { + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + class SelectorParser final : public Parser + { + public: + + // Whether this parser allows the parent selector `&`. + bool allowParent; + + // Whether this parser allows placeholder selectors beginning with `%`. + bool allowPlaceholder; + + // Value constructor + SelectorParser( + Compiler& context, + SourceDataObj source, + bool allowParent = true, + bool allowPlaceholder = true) : + Parser(context, source), + allowParent(allowParent), + allowPlaceholder(allowPlaceholder) + {} + + // Parse content into selector list + // Throws if not everything is consumed + SelectorList* parseSelectorList(); + + // Parse content into compound selector + // Throws if not everything is consumed + CompoundSelector* parseCompoundSelector(); + + // Parse content into simple selector + // Throws if not everything is consumed + SimpleSelector* parseSimpleSelector(); + + private: + + // Consumes a selector list. + SelectorList* readSelectorList(); + + // Consumes a complex selector. + ComplexSelector* readComplexSelector(bool lineBreak = false); + + // Consumes a compound selector. + CompoundSelector* readCompoundSelector(); + + // Consumes a simple selector. + SimpleSelector* readSimpleSelector(bool allowParent); + + // Consumes an attribute selector. + AttributeSelector* readAttributeSelector(); + + // Consumes an attribute name. + struct QualifiedName readAttributeName(); + + // Consumes an attribute operator. + sass::string readAttributeOperator(); + + // Consumes a class operator. + ClassSelector* readClassSelector(); + + // Consumes an in operator. + IDSelector* readIdSelector(); + + // Consumes a placeholder operator. + PlaceholderSelector* readPlaceholderSelector(); + + // Consumes a pseudo operator. + PseudoSelector* readPseudoSelector(); + + // Consumes an `an+b` expression. + sass::string readAnPlusB(); + + // Consumes a type of universal (simple) selector. + SimpleSelector* readTypeOrUniversalSelector(); + + }; + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + +} + +#endif diff --git a/src/parser_selectors.cpp b/src/parser_selectors.cpp deleted file mode 100644 index 6f60e616c4..0000000000 --- a/src/parser_selectors.cpp +++ /dev/null @@ -1,189 +0,0 @@ -// sass.hpp must go before all system headers to get the -// __EXTENSIONS__ fix on Solaris. -#include "sass.hpp" - -#include "parser.hpp" - -namespace Sass { - - using namespace Prelexer; - using namespace Constants; - - ComplexSelectorObj Parser::parseComplexSelector(bool chroot) - { - - NESTING_GUARD(nestings); - - lex < block_comment >(); - advanceToNextToken(); - - ComplexSelectorObj sel = SASS_MEMORY_NEW(ComplexSelector, pstate); - - if (peek < end_of_file >()) return sel; - - while (true) { - - lex < block_comment >(); - advanceToNextToken(); - - // check for child (+) combinator - if (lex < exactly < selector_combinator_child > >()) { - sel->append(SASS_MEMORY_NEW(SelectorCombinator, pstate, SelectorCombinator::CHILD, peek_newline())); - } - // check for general sibling (~) combinator - else if (lex < exactly < selector_combinator_general > >()) { - sel->append(SASS_MEMORY_NEW(SelectorCombinator, pstate, SelectorCombinator::GENERAL, peek_newline())); - } - // check for adjecant sibling (+) combinator - else if (lex < exactly < selector_combinator_adjacent > >()) { - sel->append(SASS_MEMORY_NEW(SelectorCombinator, pstate, SelectorCombinator::ADJACENT, peek_newline())); - } - // check if we can parse a compound selector - else if (CompoundSelectorObj compound = parseCompoundSelector()) { - sel->append(compound); - } - else { - break; - } - } - - if (sel->empty()) return {}; - - // check if we parsed any parent references - sel->chroots(sel->has_real_parent_ref() || chroot); - - sel->update_pstate(pstate); - - return sel; - - } - - SelectorListObj Parser::parseSelectorList(bool chroot) - { - - bool reloop; - bool had_linefeed = false; - NESTING_GUARD(nestings); - SelectorListObj list = SASS_MEMORY_NEW(SelectorList, pstate); - - if (peek_css< alternatives < end_of_file, exactly <'{'>, exactly <','> > >()) { - css_error("Invalid CSS", " after ", ": expected selector, was "); - } - - do { - reloop = false; - - had_linefeed = had_linefeed || peek_newline(); - - if (peek_css< alternatives < class_char < selector_list_delims > > >()) - break; // in case there are superfluous commas at the end - - // now parse the complex selector - ComplexSelectorObj complex = parseComplexSelector(chroot); - if (complex.isNull()) return list.detach(); - complex->hasPreLineFeed(had_linefeed); - - had_linefeed = false; - - while (peek_css< exactly<','> >()) - { - lex< css_comments >(false); - // consume everything up and including the comma separator - reloop = lex< exactly<','> >() != 0; - // remember line break (also between some commas) - had_linefeed = had_linefeed || peek_newline(); - // remember line break (also between some commas) - } - list->append(complex); - - } while (reloop); - - while (lex_css< kwd_optional >()) { - list->is_optional(true); - } - - // update for end position - list->update_pstate(pstate); - - return list.detach(); - } - - // parse one compound selector, which is basically - // a list of simple selectors (directly adjacent) - // lex them exactly (without skipping white-space) - CompoundSelectorObj Parser::parseCompoundSelector() - { - // init an empty compound selector wrapper - CompoundSelectorObj seq = SASS_MEMORY_NEW(CompoundSelector, pstate); - - // skip initial white-space - lex < block_comment >(); - advanceToNextToken(); - - if (lex< exactly<'&'> >(false)) - { - // ToDo: check the conditions and try to simplify flag passing - if (!allow_parent) error("Parent selectors aren't allowed here."); - // Create and append a new parent selector object - seq->hasRealParent(true); - } - - // parse list - while (true) - { - // remove all block comments - // leaves trailing white-space - lex < block_comment >(); - // parse parent selector - if (lex< exactly<'&'> >(false)) - { - // parent selector only allowed at start - // upcoming Sass may allow also trailing - SourceSpan state(pstate); - sass::string found("&"); - if (lex < identifier >()) { - found += sass::string(lexed); - } - sass::string sel(seq->hasRealParent() ? "&" : ""); - if (!seq->empty()) { sel = seq->last()->to_string({ NESTED, 5 }); } - // ToDo: parser should throw parser exceptions - error("Invalid CSS after \"" + sel + "\": expected \"{\", was \"" + found + "\"\n\n" - "\"" + found + "\" may only be used at the beginning of a compound selector."); - } - // parse functional - else if (match < re_functional >()) - { - seq->append(parse_simple_selector()); - } - - // parse type selector - else if (lex< re_type_selector >(false)) - { - seq->append(SASS_MEMORY_NEW(TypeSelector, pstate, lexed)); - } - // peek for abort conditions - else if (peek< spaces >()) break; - else if (peek< end_of_file >()) { break; } - else if (peek_css < class_char < selector_combinator_ops > >()) break; - else if (peek_css < class_char < complex_selector_delims > >()) break; - // otherwise parse another simple selector - else { - SimpleSelectorObj sel = parse_simple_selector(); - if (!sel) return {}; - seq->append(sel); - } - } - // EO while true - - if (seq && !peek_css>>()) { - seq->hasPostLineBreak(peek_newline()); - } - - // We may have set hasRealParent - if (seq && seq->empty() && !seq->hasRealParent()) return {}; - - return seq; - } - - -} diff --git a/src/parser_stylesheet.cpp b/src/parser_stylesheet.cpp new file mode 100644 index 0000000000..ed05933dd2 --- /dev/null +++ b/src/parser_stylesheet.cpp @@ -0,0 +1,4500 @@ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +/* Implementations are mostly a direct code-port from dart-sass. */ +/*****************************************************************************/ +#include "parser_stylesheet.hpp" + +#include +#include "compiler.hpp" +#include "charcode.hpp" +#include "charcode.hpp" +#include "character.hpp" +#include "color_maps.hpp" +#include "exceptions.hpp" +#include "source_span.hpp" +#include "ast_imports.hpp" +#include "ast_supports.hpp" +#include "ast_statements.hpp" +#include "ast_expressions.hpp" +#include "parser_expression.hpp" + +#include "debugger.hpp" + +namespace Sass { + + // Import some namespaces + using namespace Charcode; + using namespace Character; + using namespace StringUtils; + + ExternalCallable* StylesheetParser::parseExternalCallable() + { + // LibSass specials functions start with an `@` + bool hasAt = scanner.scanChar(Character::$at); + // Return new external callable object + ExternalCallableObj callable = + SASS_MEMORY_NEW(ExternalCallable, hasAt ? + "@" + readIdentifier() : readIdentifier(), + parseArgumentDeclaration(), nullptr); + if (!scanner.isDone()) { + error("expected selector.", + scanner.rawSpan()); + } + return callable.detach(); + } + // EO parseExternalCallable + + // Parse stylesheet root block + Root* StylesheetParser::parseRoot() + { + + // skip over optional utf8 bom + // ToDo: check influence on count + scanner.scan(Strings::utf8bom); + + // Create initial states + Offset start(scanner.offset); + + // Create new root object and setup all states + RootObj root = SASS_MEMORY_NEW(Root, scanner.rawSpan()); + // Get pointer to variables of current context + root->idxs = compiler.varRoot.stack.back(); + // Assign new module to the current context + compiler.varRoot.stack.back()->module = root; + + // Set the current module context + RAII_MODULE(modules, root); + RAII_PTR(Root, modctx, root); + + // Get reference to (not yet) parsed children + StatementVector& children(root->elements()); + + // Check seems a bit esoteric but works + if (compiler.included_sources.size() == 1) { + // Apply headers only on very first include + compiler.applyCustomHeaders(children, + scanner.relevantSpanFrom(start)); + } + + // Parse nested root statements now + StatementVector parsed(readStatements( + &StylesheetParser::readRootStatement)); + + // Move parsed children into our array + children.insert(children.end(), + std::make_move_iterator(parsed.begin()), + std::make_move_iterator(parsed.end())); + + // Ensure everything is parsed + scanner.expectDone(); + + // Update parser state after we are done + root->pstate(scanner.relevantSpanFrom(start)); + + // Return root object + return root.detach(); + } + // EO parseRoot + + // Consumes a statement that's allowed at the top level of the stylesheet or + // within nested style and at rules. If [root] is `true`, this parses at-rules + // that are allowed only at the root of the stylesheet. + Statement* StylesheetParser::readStatement(bool root) + { + inRoot = root; // expose + Offset start(scanner.offset); + switch (scanner.peekChar()) { + + case $at: + return readAtRule(&StylesheetParser::readChildStatement, root); + + case $plus: + if (!isIndented() || !lookingAtIdentifier(1)) { + return readStyleRule(); + } + isUseAllowed = false; + start = scanner.offset; + scanner.readChar(); + return readIncludeRule(start); + + case $equal: + if (!isIndented()) return readStyleRule(); + isUseAllowed = false; + start = scanner.offset; + scanner.readChar(); + scanWhitespace(); + return readMixinRule(start); + + default: + if (inStyleRule || inUnknownAtRule || inMixin || inContentBlock) { + return readDeclarationOrStyleRule(); + } + else { + return readVariableDeclarationOrStyleRule(); + } + + } + return nullptr; + } + // EO readStatement + + Expression* StylesheetParser::readNamespacedExpression( + const sass::string& ns, Offset start) + { + if (scanner.peekChar() == $dollar) { + auto name = variableName(); + // _assertPublic(name, () = > scanner.spanFrom(start)); + return new VariableExpression( + scanner.relevantSpanFrom(start), + name, ns); + } + sass::string name(readPublicIdentifier()); + CallableArgumentsObj args(readArgumentInvocation()); + return new FunctionExpression( + scanner.relevantSpanFrom(start), + std::move(name), args, ns); + } + + // Consumes an `@import` rule. + // [start] should point before the `@`. + ImportRule* StylesheetParser::readImportRule(Offset start) + { + ImportRuleObj rule = SASS_MEMORY_NEW( + ImportRule, scanner.relevantSpanFrom(start)); + + do { + scanWhitespace(); + scanImportArgument(rule); + scanWhitespace(); + } while (scanner.scanChar($comma)); + // Check for expected finalization token + expectStatementSeparator("@import rule"); + return rule.detach(); + } + // EO readImportRule + + // Consumes an argument to an `@import` rule. + // If anything is found it will be added to [rule]. + void StylesheetParser::scanImportArgument(ImportRule* rule) + { + const char* startpos = scanner.position; + Offset start(scanner.offset); + uint8_t next = scanner.peekChar(); + if (next == $u || next == $U) { + Expression* url = readFunctionOrStringExpression(); + scanWhitespace(); + auto modifiers = tryImportModifiers(); + rule->append(SASS_MEMORY_NEW(StaticImport, + scanner.relevantSpanFrom(start), + SASS_MEMORY_NEW(Interpolation, + url->pstate(), url), + modifiers, inRoot)); + return; + } + + sass::string url = string(); + const char* rawUrlPos = scanner.position; + SourceSpan pstate = scanner.relevantSpanFrom(start); + scanWhitespace(); + auto modifiers = tryImportModifiers(); + if (isPlainImportUrl(url) || modifiers != nullptr) { + // Create static import that is never + // resolved by libsass (output as is) + rule->append(SASS_MEMORY_NEW(StaticImport, + scanner.relevantSpanFrom(start), + SASS_MEMORY_NEW(Interpolation, pstate, + SASS_MEMORY_NEW(String, pstate, + sass::string(startpos, rawUrlPos))), + modifiers, inRoot)); + } + // Otherwise return a dynamic import + // Will resolve during the eval stage + else { + // Check for valid dynamic import + if (inControlDirective || inMixin) { + throwDisallowedAtRule(rule->pstate().position); + } + + SourceSpan pstate(scanner.relevantSpanFrom(start)); + if (!compiler.callCustomImporters(url, pstate, rule)) { + rule->append(SASS_MEMORY_NEW(IncludeImport, + pstate, scanner.sourceUrl, url, nullptr)); + } + + } + + } + // EO scanImportArgument + + + // Tries to parse a name-spaced [VariableDeclaration], and returns the value + // parsed so far if it fails. + // + // This can return either an [Interpolation], indicating that it couldn't + // consume a variable declaration and that property declaration or selector + // parsing should be attempted; or it can return a [VariableDeclaration], + // indicating that it successfully consumed a variable declaration. + bool StylesheetParser::tryVariableDeclarationOrInterpolation( + AssignRule*& assignment, Interpolation*& interpolation) + { + + if (!lookingAtIdentifier()) { + interpolation = readInterpolatedIdentifier(); + return true; + } + + Offset start(scanner.offset); + sass::string identifier(readIdentifier()); + if (scanner.matches(".$")) { + scanner.readChar(); + assignment = readVariableDeclarationWithoutNamespace(identifier, start); + return true; + } + else { + + ItplStringObj prefix(SASS_MEMORY_NEW(ItplString, + scanner.relevantSpanFrom(start), identifier)); + + // Parse the rest of an interpolated identifier + // if one exists, so callers don't have to. + if (lookingAtInterpolatedIdentifierBody()) { + interpolation = readInterpolatedIdentifier(); + interpolation->unshift(prefix.ptr()); + } + else { + interpolation = SASS_MEMORY_NEW(Interpolation, + scanner.relevantSpanFrom(start), prefix); + } + + return true; + } + + return false; + } + + AssignRule* StylesheetParser::readVariableDeclarationWithNamespace() + { + Offset start(scanner.offset); + sass::string ns(readIdentifier()); + scanner.expectChar($dot); + return readVariableDeclarationWithoutNamespace(ns, start); + } + + // Returns whether [identifier] is module-private. + // Assumes [identifier] is a valid Sass identifier. + bool isPrivate(const sass::string& identifier) + { + return identifier[0] == $minus || + identifier[0] == $underscore; + } + // EO isPrivate + + // Throws an error if [identifier] isn't public. + void StylesheetParser::assertPublicIdentifier( + const sass::string& identifier, Offset start) + { + if (!isPrivate(identifier)) return; + error("Private members can't be accessed from outside their modules.", + scanner.relevantSpanFrom(start)); + } + // EO assertPublicIdentifier + + // Consumes a style rule. + StyleRule* StylesheetParser::readStyleRule(Interpolation* itpl) + { + isUseAllowed = false; + RAII_FLAG(inStyleRule, true); + + // The indented syntax allows a single backslash to distinguish a style rule + // from old-style property syntax. We don't support old property syntax, but + // we do support the backslash because it's easy to do. + if (isIndented()) scanner.scanChar($backslash); + InterpolationObj readStyleRule(styleRuleSelector()); + if (itpl) { + itpl->concat(readStyleRule); readStyleRule = itpl; + readStyleRule->pstate(scanner.rawSpanFrom(itpl->pstate().position)); + } + EnvFrame local(compiler, false); + + Offset start(scanner.offset); + StyleRuleObj styles = withChildren( + &StylesheetParser::readChildStatement, + start, readStyleRule.ptr(), local.idxs); + + if (isIndented() && styles->empty()) { + compiler.addWarning( + "This selector doesn't have any properties and won't be rendered.", + itpl ? itpl->pstate() : SourceSpan{}, Logger::WARN_EMPTY_SELECTOR); + } + + return styles.detach(); + + } + // EO readStyleRule + + // Consumes a [Declaration] or a [StyleRule]. + // + // When parsing the contents of a style rule, it can be difficult to tell + // declarations apart from nested style rules. Since we don't thoroughly + // parse selectors until after resolving interpolation, we can share a bunch + // of the parsing of the two, but we need to disambiguate them first. We use + // the following criteria: + // + // * If the entity doesn't start with an identifier followed by a colon, + // it's a selector. There are some additional mostly-unimportant cases + // here to support various declaration hacks. + // + // * If the colon is followed by another colon, it's a selector. + // + // * Otherwise, if the colon is followed by anything other than + // interpolation or a character that's valid as the beginning of an + // identifier, it's a declaration. + // + // * If the colon is followed by interpolation or a valid identifier, try + // parsing it as a declaration value. If this fails, backtrack and parse + // it as a selector. + // + // * If the declaration value is valid but is followed by "{", backtrack and + // parse it as a selector anyway. This ensures that ".foo:bar {" is always + // parsed as a selector and never as a property with nested properties + // beneath it. + Statement* StylesheetParser::readDeclarationOrStyleRule() + { + + if (plainCss() && inStyleRule && !inUnknownAtRule) { + return readPropertyOrVariableDeclaration(); + } + + // The indented syntax allows a single backslash to distinguish a style rule + // from old-style property syntax. We don't support old property syntax, but + // we do support the backslash because it's easy to do. + if (isIndented() && scanner.scanChar($backslash)) { + return readStyleRule(); + } + + Offset start(scanner.offset); + InterpolationBuffer buffer(scanner); + Statement* declaration = tryDeclarationOrBuffer(buffer); + + if (declaration != nullptr) { + return declaration; + } + + // This differs from dart-sass + + buffer.addInterpolation(styleRuleSelector()); + // SourceSpan selectorPstate(scanner.relevantSpanFrom(start)); + SourceSpan selectorPstate(scanner.rawSpanFrom(start)); + + RAII_FLAG(inStyleRule, true); + + if (buffer.empty()) { + error("expected \"}\".", + scanner.relevantSpan()); + } + + EnvFrame local(compiler, true); + InterpolationObj itpl = buffer.getInterpolation( + scanner.rawSpanFrom(start)); + StyleRuleObj rule = withChildren( + &StylesheetParser::readChildStatement, + start, itpl.ptr(), local.idxs); + if (isIndented() && rule->empty()) { + compiler.addWarning( + "This selector doesn't have any properties and won't be rendered.", + selectorPstate, Logger::WARN_EMPTY_SELECTOR); + } + return rule.detach(); + } + // readDeclarationOrStyleRule + + Statement* StylesheetParser::readVariableDeclarationOrStyleRule() + { + + if (plainCss()) return readStyleRule(); + + // The indented syntax allows a single backslash to distinguish a style rule + // from old-style property syntax. We don't support old property syntax, but + // we do support the backslash because it's easy to do. + if (isIndented() && scanner.scanChar($backslash)) return readStyleRule(); + + if (!lookingAtIdentifier()) return readStyleRule(); + + // Offset start(scanner.offset); + AssignRule* assignment = nullptr; + Interpolation* interpolation = nullptr; + tryVariableDeclarationOrInterpolation(assignment, interpolation); + if (assignment) return assignment; + return readStyleRule(interpolation); // , start + } + + + // Tries to parse a declaration, and returns the value parsed so + // far if it fails. This can return either an [InterpolationBuffer], + // indicating that it couldn't consume a declaration and that selector + // parsing should be attempted; or it can return a [Declaration], + // indicating that it successfully consumed a declaration. + Statement* StylesheetParser::tryDeclarationOrBuffer(InterpolationBuffer& nameBuffer) + { + Offset start(scanner.offset); + + // Allow the "*prop: val", ":prop: val", + // "#prop: val", and ".prop: val" hacks. + uint8_t first = scanner.peekChar(); + bool startsWithPunctuation = false; + if (first == $colon || first == $asterisk || first == $dot || + (first == $hash && scanner.peekChar(1) != $lbrace)) { + sass::sstream strm; + startsWithPunctuation = true; + strm << scanner.readChar(); + strm << rawText(&StylesheetParser::scanWhitespace); + nameBuffer.write(strm.str(), scanner.relevantSpanFrom(start)); + } + + if (!lookingAtInterpolatedIdentifier()) { + return nullptr; + } + + if (startsWithPunctuation == false) { + Interpolation* itpl = nullptr; + AssignRule* assignment = nullptr; + tryVariableDeclarationOrInterpolation(assignment, itpl); + if (assignment != nullptr) return assignment; + if (itpl) nameBuffer.addInterpolation(itpl); + } + else { + nameBuffer.addInterpolation(readInterpolatedIdentifier()); + } + + isUseAllowed = false; + if (scanner.matches("/*")) nameBuffer.write(rawText(&StylesheetParser::scanLoudComment)); + + StringBuffer midBuffer; + midBuffer.write(rawText(&StylesheetParser::scanWhitespace)); + SourceSpan beforeColon(scanner.relevantSpanFrom(start)); + if (!scanner.scanChar($colon)) { + if (!midBuffer.empty()) { + nameBuffer.write($space); + } + return nullptr; + } + midBuffer.write($colon); + + // Parse custom properties as declarations no matter what. + InterpolationObj name = nameBuffer.getInterpolation(beforeColon); + if (startsWith(name->getInitialPlain(), "--")) { + InterpolationObj value(readInterpolatedDeclarationValue()); + expectStatementSeparator("custom property"); + return SASS_MEMORY_NEW(Declaration, + scanner.relevantSpanFrom(start), name, + value->wrapInStringExpression(), true); + } + + if (scanner.scanChar($colon)) { + nameBuffer.write(midBuffer.buffer); + nameBuffer.write($colon); + return nullptr; + } + else if (isIndented() && lookingAtInterpolatedIdentifier()) { + // In the indented syntax, `foo:bar` is always + // considered a selector rather than a property. + nameBuffer.write(midBuffer.buffer); + return nullptr; + } + + sass::string postColonWhitespace = rawText(&StylesheetParser::scanWhitespace); + if (lookingAtChildren()) { + return withChildren( + &StylesheetParser::readDeclarationOrAtRule, + start, name, nullptr, false); + } + + midBuffer.write(postColonWhitespace); + bool couldBeSelector = postColonWhitespace.empty() + && lookingAtInterpolatedIdentifier(); + + StringScannerState beforeDeclaration = scanner.state(); + ExpressionObj value; + + try { + + if (lookingAtChildren()) { + SourceSpan pstate = scanner.relevantSpanFrom(scanner.offset); + Interpolation* itpl = SASS_MEMORY_NEW(Interpolation, pstate); + value = SASS_MEMORY_NEW(StringExpression, pstate, itpl, true); + } + else { + value = readExpression(); + } + + if (lookingAtChildren()) { + // Properties that are ambiguous with selectors can't have additional + // properties nested beneath them, so we force an error. This will be + // caught below and cause the text to be re-parsed as a selector. + if (couldBeSelector) { + expectStatementSeparator(); + } + } + else if (!atEndOfStatement()) { + // Force an exception if there isn't a valid end-of-property character but + // don't consume that character. This will also cause text to be re-parsed. + expectStatementSeparator(); + } + + } + catch (Exception::ParserException&) { + if (!couldBeSelector) throw; + + // If the value would be followed by a semicolon, it's + // definitely supposed to be a property, not a selector. + scanner.backtrack(beforeDeclaration); + InterpolationObj additional = readAlmostAnyValue(); + if (!isIndented() && scanner.peekChar() == $semicolon) throw; + + nameBuffer.write(midBuffer.buffer); + nameBuffer.addInterpolation(additional); + return nullptr; + } + + if (lookingAtChildren()) { + // Offset start(scanner.offset); + return withChildren( + &StylesheetParser::readDeclarationOrAtRule, + start, name, value, false); + } + else { + expectStatementSeparator(); + return SASS_MEMORY_NEW(Declaration, + scanner.relevantSpanFrom(start), name, value); + } + + } + // EO tryDeclarationOrBuffer + + // Consumes a property declaration. This is only used in contexts where + // declarations are allowed but style rules are not, such as nested + // declarations. Otherwise, [readDeclarationOrStyleRule] is used instead. + Statement* StylesheetParser::readPropertyOrVariableDeclaration(bool parseCustomProperties) + { + + Offset start(scanner.offset); + + InterpolationObj name; + // Allow the "*prop: val", ":prop: val", + // "#prop: val", and ".prop: val" hacks. + uint8_t first = scanner.peekChar(); + if (first == $colon || + first == $asterisk || + first == $dot || + (first == $hash && scanner.peekChar(1) != $lbrace)) { + InterpolationBuffer nameBuffer(scanner); + nameBuffer.write(scanner.readChar()); + nameBuffer.write(rawText(&StylesheetParser::scanWhitespace)); + nameBuffer.addInterpolation(readInterpolatedIdentifier()); + name = nameBuffer.getInterpolation(scanner.relevantSpanFrom(start)); + } + else if (!plainCss()) { + AssignRule* assignment = nullptr; + Interpolation* interpolation = nullptr; + tryVariableDeclarationOrInterpolation(assignment, interpolation); + if (assignment) return assignment; else name = interpolation; + } + else { + name = readInterpolatedIdentifier(); + } + + scanWhitespace(); + scanner.expectChar($colon); + scanWhitespace(); + + if (parseCustomProperties && startsWith(name->getInitialPlain(), "--", 2)) { + InterpolationObj value(readInterpolatedDeclarationValue()); + expectStatementSeparator("custom property"); + return SASS_MEMORY_NEW(Declaration, + scanner.relevantSpanFrom(start), + name, value->wrapInStringExpression()); + } + + if (lookingAtChildren()) { + if (plainCss()) { + error("Nested declarations aren't allowed in plain CSS.", + scanner.rawSpan()); + } + return withChildren( + &StylesheetParser::readDeclarationOrAtRule, start, name, + nullptr, startsWith(name->getInitialPlain(), "--", 2)); + } + + ExpressionObj value = readExpression(); + if (lookingAtChildren()) { + if (plainCss()) { + error("Nested declarations aren't allowed in plain CSS.", + scanner.rawSpan()); + } + // only without children; + return withChildren( + &StylesheetParser::readDeclarationOrAtRule, + start, name, value, + startsWith(name->getInitialPlain(), "--", 2)); + } + else { + expectStatementSeparator(); + return SASS_MEMORY_NEW(Declaration, + scanner.relevantSpanFrom(start), name, value, + startsWith(name->getInitialPlain(), "--", 2)); + } + + } + // EO readPropertyOrVariableDeclaration + + // Consumes a statement that's allowed within a declaration. + Statement* StylesheetParser::readDeclarationOrAtRule() // _declarationChild + { + if (scanner.peekChar() == $at) { + return readDeclarationAtRule(); + } + return readPropertyOrVariableDeclaration(false); + } + // EO readDeclarationOrAtRule + + // Consumes an at-rule. This consumes at-rules that are allowed at all levels + // of the document; the [child] parameter is called to consume any at-rules + // that are specifically allowed in the caller's context. If [root] is `true`, + // this parses at-rules that are allowed only at the root of the stylesheet. + Statement* StylesheetParser::readAtRule(Statement* (StylesheetParser::* child)(), bool root) + { + // NOTE: this logic is largely duplicated in CssParser.atRule. + // Most changes here should be mirrored there. + + Offset start(scanner.offset); + scanner.expectChar($at, "@-rule"); + InterpolationObj name = readInterpolatedIdentifier(); + scanWhitespace(); + + // We want to set [isUseAllowed] to `false` *unless* we're parsing + // `@charset`, `@forward`, or `@use`. To avoid double-comparing the rule + // name, we always set it to `false` and then set it back to its previous + // value if we're parsing an allowed rule. + bool wasUseAllowed = isUseAllowed; + isUseAllowed = false; + + sass::string plain(name->getPlainString()); + if (plain == "at-root") { + return readAtRootRule(start); + } + else if (plain == "charset") { + isUseAllowed = wasUseAllowed; + if (!root) throwDisallowedAtRule(start); + sass::string throwaway(string()); + return nullptr; + } + else if (plain == "content") { + return readContentRule(start); + } + else if (plain == "debug") { + return readDebugRule(start); + } + else if (plain == "each") { + return readEachRule(start, child); + } + else if (plain == "else") { + return throwDisallowedAtRule(start); + } + else if (plain == "error") { + return readErrorRule(start); + } + else if (plain == "extend") { + return readExtendRule(start); + } + else if (plain == "for") { + return readForRule(start, child); + } + else if (plain == "function") { + return readFunctionRule(start); + } + else if (plain == "if") { + return readIfRule(start, child); + } + else if (plain == "import") { + return readImportRule(start); + } + else if (plain == "include") { + return readIncludeRule(start); + } + else if (plain == "media") { + return readMediaRule(start); + } + else if (plain == "mixin") { + return readMixinRule(start); + } + else if (plain == "-moz-document") { + return readMozDocumentRule(start, name); + } + else if (plain == "return") { + return throwDisallowedAtRule(start); + } + else if (plain == "supports") { + return readSupportsRule(start); + } + else if (plain == "use") { + // return readAnyAtRule(start, name); + isUseAllowed = wasUseAllowed; + if (!root) throwDisallowedAtRule(start); + return readUseRule(start); + } + else if (plain == "forward") { + // return readAnyAtRule(start, name); + isUseAllowed = wasUseAllowed; + if (!root) throwDisallowedAtRule(start); + return readForwardRule(start); + } + else if (plain == "warn") { + return readWarnRule(start); + } + else if (plain == "while") { + return readWhileRule(start, child); + } + else { + return readAnyAtRule(start, name); + } + + } + // EO atRule + + // Consumes an at-rule allowed within a property declaration. + Statement* StylesheetParser::readDeclarationAtRule() + { + Offset start(scanner.offset); + sass::string name = readPlainAtRuleName(); + + if (name == "content") { + return readContentRule(start); + } + else if (name == "debug") { + return readDebugRule(start); + } + else if (name == "each") { + return readEachRule(start, + &StylesheetParser::readDeclarationOrAtRule); + } + else if (name == "else") { + return throwDisallowedAtRule(start); + } + else if (name == "error") { + return readErrorRule(start); + } + else if (name == "for") { + return readForRule(start, + &StylesheetParser::readDeclarationOrAtRule); + } + else if (name == "if") { + return readIfRule(start, + &StylesheetParser::readDeclarationOrAtRule); + } + else if (name == "include") { + return readIncludeRule(start); + } + else if (name == "warn") { + return readWarnRule(start); + } + else if (name == "while") { + return readWhileRule(start, + &StylesheetParser::readDeclarationOrAtRule); + } + else { + return throwDisallowedAtRule(start); + } + } + + // Consumes a statement allowed within a function. + Statement* StylesheetParser::readFunctionRuleChild() + { + if (scanner.peekChar() != $at) { + + StringScannerState state(scanner.state()); + try { return readVariableDeclarationWithNamespace(); } + // dart-sass does some error cosmetic here + catch (...) { + + scanner.backtrack(state); + + } + + // If a variable declaration failed to parse, it's possible the user + // thought they could write a style rule or property declaration in a + // function. If so, throw a more helpful error message. + StatementObj statement(readDeclarationOrStyleRule()); + // ToDo: dart-sass has a try/catch clause here!? + bool isStyleRule = statement->isaStyleRule() != nullptr; + error( + sass::string("@function rules may not contain ") + + (isStyleRule ? "style rules." : "declarations."), + statement->pstate()); + } + + Offset start(scanner.offset); + sass::string name(readPlainAtRuleName()); + if (name == "debug") { + return readDebugRule(start); + } + else if (name == "each") { + return readEachRule(start, + &StylesheetParser::readFunctionRuleChild); + } + else if (name == "else") { + return throwDisallowedAtRule(start); + } + else if (name == "error") { + return readErrorRule(start); + } + else if (name == "for") { + return readForRule(start, + &StylesheetParser::readFunctionRuleChild); + } + else if (name == "if") { + return readIfRule(start, + &StylesheetParser::readFunctionRuleChild); + } + else if (name == "return") { + return readReturnRule(start); + } + else if (name == "warn") { + return readWarnRule(start); + } + else if (name == "while") { + return readWhileRule(start, + &StylesheetParser::readFunctionRuleChild); + } + else { + return throwDisallowedAtRule(start); + } + } + + // Consumes an at-rule's name, with interpolation disallowed. + sass::string StylesheetParser::readPlainAtRuleName() + { + scanner.expectChar($at, "@-rule"); + sass::string name = readIdentifier(); + scanWhitespace(); + return name; + } + + // Consumes an `@at-root` rule. + // [start] should point before the `@`. + AtRootRule* StylesheetParser::readAtRootRule(Offset start) + { + + EnvFrame local(compiler, false); + + if (scanner.peekChar() == $lparen) { + InterpolationObj query = readAtRootQuery(); + scanWhitespace(); + return withChildren( + &StylesheetParser::readChildStatement, + start, query, local.idxs); + } + else if (lookingAtChildren()) { + return withChildren( + &StylesheetParser::readChildStatement, + start, nullptr, local.idxs); + } + StyleRule* child = readStyleRule(); + return SASS_MEMORY_NEW(AtRootRule, + scanner.relevantSpanFrom(start), + nullptr, local.idxs, { child }); + } + // EO readAtRootRule + + // Consumes a query expression of the form `(foo: bar)`. + Interpolation* StylesheetParser::readAtRootQuery() + { + if (scanner.peekChar() == $hash) { + Expression* interpolation(readSingleInterpolation()); + return SASS_MEMORY_NEW(Interpolation, + interpolation->pstate(), interpolation); + } + + Offset start(scanner.offset); + InterpolationBuffer buffer(scanner); + scanner.expectChar($lparen); + buffer.writeCharCode($lparen); + scanWhitespace(); + + buffer.add(readExpression()); + if (scanner.scanChar($colon)) { + scanWhitespace(); + buffer.writeCharCode($colon); + buffer.writeCharCode($space); + buffer.add(readExpression()); + } + + scanner.expectChar($rparen); + scanWhitespace(); + buffer.writeCharCode($rparen); + + return buffer.getInterpolation( + scanner.relevantSpanFrom(start)); + + } + + // Consumes a `@content` rule. + // [start] should point before the `@`. + ContentRule* StylesheetParser::readContentRule(Offset start) + { + if (!inMixin) { + error("@content is only allowed within mixin declarations.", + scanner.relevantSpanFrom(start)); + } + + scanWhitespace(); + + CallableArgumentsObj args; + if (scanner.peekChar() == $lparen) { + args = readArgumentInvocation(true); + } + else { + args = SASS_MEMORY_NEW(CallableArguments, + scanner.relevantSpan(), ExpressionVector(), {}); + } + + RAII_FLAG(mixinHasContent, true); + expectStatementSeparator("@content rule"); + // ToDo: ContentRule + return SASS_MEMORY_NEW(ContentRule, + scanner.relevantSpanFrom(start), args); + + } + // EO readContentRule + + // Try to parse either `to` or `through`, if successful + // we will return `true`. The boolean passed via [inclusive] + // will be set to `true` if we parsed `through`. We return + // `false` if neither of the tokens could be parsed. + bool StylesheetParser::tryForRuleOperator(bool& inclusive) + { + if (!lookingAtIdentifier()) return false; + if (scanIdentifier("to")) { + inclusive = false; + return true; + } + else if (scanIdentifier("through")) { + inclusive = true; + return true; + } + else { + return false; + } + } + + + // Consumes an `@each` rule. + // [start] should point before the `@`. [child] is called to consume any + // children that are specifically allowed in the caller's context. + EachRule* StylesheetParser::readEachRule(Offset start, Statement* (StylesheetParser::* child)()) + { + // This must be enabled to pass tests + RAII_FLAG(inControlDirective, true); + sass::vector variables; + EnvFrame local(compiler, true); + variables.emplace_back(variableName()); + local.idxs->createVariable(variables.back()); + scanWhitespace(); + while (scanner.scanChar($comma)) { + scanWhitespace(); + variables.emplace_back(variableName()); + local.idxs->createVariable(variables.back()); + scanWhitespace(); + } + expectIdentifier("in", "\"in\""); + scanWhitespace(); + ExpressionObj list = readExpression(); + return withChildren( + child, start, variables, list, local.idxs); + } + + ErrorRule* StylesheetParser::readErrorRule(Offset start) + { + ExpressionObj value = readExpression(); + expectStatementSeparator("@error rule"); + return SASS_MEMORY_NEW(ErrorRule, + scanner.relevantSpanFrom(start), value); + } + // EO readErrorRule + + // Consumes an `@extend` rule. + // [start] should point before the `@`. + ExtendRule* StylesheetParser::readExtendRule(Offset start) + { + if (!inStyleRule && !inMixin && !inContentBlock) { + error("@extend may only be used within style rules.", + scanner.relevantSpanFrom(start)); + } + + InterpolationObj value = readAlmostAnyValue(); + bool optional = scanner.scanChar($exclamation); + if (optional) expectIdentifier("optional", "\"optional\""); + expectStatementSeparator("@extend rule"); + return SASS_MEMORY_NEW(ExtendRule, + scanner.relevantSpanFrom(start), value, optional); + } + // EO readExtendRule + + // Returns `true` if scanner reached a `,` + bool StylesheetParser::lookingAtForRuleContinuation() + { + if (!lookingAtIdentifier()) return false; + if (scanIdentifier("to")) { + _foundForRuleExpression = true; + _exclusiveAtForRule = true; + return true; + } + else if (scanIdentifier("through")) { + _foundForRuleExpression = true; + _exclusiveAtForRule = false; + return true; + } + else { + return false; + } + } + + + ForRule* StylesheetParser::readForRule(Offset start, Statement* (StylesheetParser::* child)()) + { + RAII_FLAG(inControlDirective, true); + EnvFrame local(compiler, true); + sass::string variable = variableName(); + local.idxs->createVariable(variable); + scanWhitespace(); + expectIdentifier("from", "\"from\""); + scanWhitespace(); + // ExpressionObj from = readSingleExpression(); + _exclusiveAtForRule = false; + _foundForRuleExpression = false; + ExpressionObj from = readExpression(false, false, + &StylesheetParser::lookingAtForRuleContinuation); + if (!_foundForRuleExpression) { + error("Expected \"to\" or \"through\".", + scanner.relevantSpan()); + } + + scanWhitespace(); + ExpressionObj to = readExpression(); + auto qwe = withChildren(child, start, + variable, from, to, !_exclusiveAtForRule, local.idxs); + return qwe; + } + + // ToDo: dart-sass stores all else ifs in the same object, smart ... + IfRule* StylesheetParser::readIfRule(Offset start, Statement* (StylesheetParser::* child)()) + { + // var ifIndentation = currentIndentation; + size_t ifIndentation = 0; + RAII_FLAG(inControlDirective, true); + ExpressionObj predicate = readExpression(); + + IfRuleObj root; + IfRuleObj cur; + + /* create anonymous lexical scope */ { + EnvFrame local(compiler, true); + StatementVector children( + readChildren(child)); + cur = root = SASS_MEMORY_NEW(IfRule, + scanner.relevantSpanFrom(start), local.idxs, + std::move(children), std::move(predicate)); + } + // EO lexical scope + + scanWhitespaceWithoutComments(); + + sass::vector ifs; + ifs.push_back(root); + + while (scanElse(ifIndentation)) { + scanWhitespace(); + // scanned a else if + if (scanIdentifier("if")) { + scanWhitespace(); + + ExpressionObj predicate = readExpression(); + start = scanner.offset; + + EnvFrame local(compiler, true); + StatementVector children( + readChildren(child)); + IfRule* alternative = SASS_MEMORY_NEW(IfRule, + scanner.relevantSpanFrom(start), local.idxs, + std::move(children), std::move(predicate)); + cur->alternative(alternative); + cur = alternative; + } + // scanned a pure else + else { + + EnvFrame local(compiler, true); + + start = scanner.offset; + StatementVector children( + readChildren(child)); + IfRule* alternative = SASS_MEMORY_NEW(IfRule, + scanner.relevantSpanFrom(start), local.idxs, + std::move(children)); // else has no predicate + cur->alternative(alternative); + break; + } + } + + scanWhitespaceWithoutComments(); + + return root.detach(); + + } + + /// Parses the namespace of a `@use` rule from an `as` clause, or returns the + /// default namespace from its URL. + sass::string StylesheetParser::readUseNamespace(const sass::string& url, const Offset& start) + { + if (scanIdentifier("as")) { + scanWhitespace(); + return scanner.scanChar($asterisk) + ? "*" : readIdentifier(); + } + + // Check if name is valid identifier + if (url.empty() || isDigit(url[0])) { + SourceSpan pstate(scanner.relevantSpanFrom(start)); + callStackFrame csf(compiler, pstate); + throw Exception::InvalidDefaultNamespace(compiler, url); + } + + return ""; + } + + bool StylesheetParser::readWithConfiguration( + sass::vector& vars, + bool allowGuarded) + { + + if (!scanIdentifier("with")) return false; + + scanWhitespace(); + scanner.expectChar($lparen); + + std::set seen; + + while (true) { + scanWhitespace(); + + Offset variableStart(scanner.offset); + sass::string name(variableName()); + scanWhitespace(); + scanner.expectChar($colon); + scanWhitespace(); + ExpressionObj expression = readExpressionUntilComma(); + + bool guarded = false; + Offset flagStart(scanner.offset); + if (allowGuarded && scanner.scanChar($exclamation)) { + sass::string flag(readIdentifier()); + if (flag == "default") { + guarded = true; + } + else { + error("Invalid flag name.", + scanner.relevantSpanFrom(flagStart)); + } + } + + if (seen.count(name) == 1) { + error("The same variable may only be configured once.", + scanner.relevantSpanFrom(variableStart)); + } + + seen.insert(name); + + WithConfigVar kvar; + kvar.expression44 = expression; + kvar.isGuarded41 = guarded; + kvar.pstate = scanner.relevantSpanFrom(variableStart); + kvar.name = name; + vars.push_back(kvar); + + if (!scanner.scanChar($comma)) break; + scanWhitespace(); + if (!lookingAtExpression()) break; + } + + scanWhitespace(); + scanner.expectChar($rparen); + return true; + } + + void StylesheetParser::readForwardMembers(std::set& variables, std::set& callables) + { + try { + do { + scanWhitespace(); + if (scanner.peekChar() == $dollar) { + variables.insert(variableName()); + } + else { + callables.insert(readIdentifier()); + } + scanWhitespace(); + } while (scanner.scanChar($comma)); + } + // ToDo: Profile how expensive this is + catch (Exception::ParserException& err) { + err.msg = "Expected variable, mixin, or function name"; + throw err; + } + } + + // Returns whether [url] indicates that an `@import` is a plain CSS import. + bool StylesheetParser::isPlainImportUrl(const sass::string& url) const + { + if (url.length() < 5) return false; + + if (endsWithIgnoreCase(url, ".css", 4)) return true; + + uint8_t first = url[0]; + if (first == $slash) return url[1] == $slash; + if (first != $h) return false; + return startsWithIgnoreCase(url, "http://", 7) + || startsWithIgnoreCase(url, "https://", 6); + } + + // Consumes a sequence of modifiers (such as media or supports queries) + // after an import argument. Returns `null` if there are no modifiers. + Interpolation* StylesheetParser::tryImportModifiers() + { + if (!lookingAtInterpolatedIdentifier() && scanner.peekChar() != $lparen) { + return nullptr; + } + + Offset start = scanner.offset; + InterpolationBuffer buffer(scanner); + while (true) { + // std::cerr << "====\n"; + if (lookingAtInterpolatedIdentifier()) { + // std::cerr << "Looki looki\n"; + // if (!buffer.empty()) buffer.writeCharCode($space); + + InterpolationObj identifier = readInterpolatedIdentifier(); + if (identifier == nullptr) std::cerr << "IS NULL"; + // std::cerr << "got ident\n"; + if (!buffer.empty()) buffer.writeCharCode($space); + buffer.addInterpolation(identifier); + // std::cerr << " asddde " << identifier << "\n"; + + auto name = identifier->getPlainString(); + + // std::cerr << "is it " << name.c_str() << "\n"; + + // convert to lower case + if (!equalsIgnoreCase(name, "and", 3) && scanner.scanChar($lparen)) { + // std::cerr << "is and\n"; + if (equalsIgnoreCase(name, "supports", 8)) { + auto query = readImportSupportsQuery(); + if (!query->isaSupportsDeclaration()) + buffer.writeCharCode($lparen); + auto foo = SASS_MEMORY_NEW(SupportsExpression, + scanner.rawSpanFrom(start), query); + // std::cerr << "0 ++ " << foo->toString() << "\n"; + buffer.add(foo); + if (!query->isaSupportsDeclaration()) + buffer.writeCharCode($rparen); + } + else { + buffer.writeCharCode($lparen); + auto itpl = readInterpolatedDeclarationValue(true, true); + // std::cerr << "1 ++ " << itpl->toString() << "\n"; + buffer.addInterpolation(itpl); + buffer.writeCharCode($rparen); + } + + scanner.expectChar($rparen); + scanWhitespace(); + } + else { + // std::cerr << "is other\n"; + scanWhitespace(); + if (scanner.scanChar($comma)) { + buffer.write(", "); + buffer.addInterpolation(readMediaQueryList()); + // std::cerr << "return 2\n"; + return buffer.getInterpolation( + scanner.relevantSpanFrom(start)); + } + } + } + else { + // std::cerr << "checki checki " << scanner.peekChar() << "\n"; + if (scanner.peekChar() == $lparen) { + // std::cerr << "HEPPA\n"; + if (!buffer.empty()) buffer.writeCharCode($space); + auto foo = readMediaQueryList(); + // std::cerr << "2 ++ " << foo->toString() << "\n"; + buffer.addInterpolation(foo); + return buffer.getInterpolation( + scanner.relevantSpanFrom(start)); + } + else { + // std::cerr << "return 3\n"; + return buffer.getInterpolation( + scanner.relevantSpanFrom(start)); + } + } + } + // std::cerr << "ended\n";f + } + // EO tryImportModifiers + + // Consumes the contents of a `supports()` function after + // an `@import` rule (but not the function name or parentheses). + SupportsCondition* StylesheetParser::readImportSupportsQuery() { + if (scanIdentifier("not")) { + scanWhitespace(); + Offset start(scanner.offset); + StringScannerState state(scanner.state()); + return new SupportsNegation( + scanner.rawSpanFrom(start), + readSupportsConditionInParens()); + } + else if (scanner.peekChar() == $lparen) { + return readSupportsCondition(); + } + else { + auto function = tryImportSupportsFunction(); + if (function != nullptr) return function; + + Offset start(scanner.offset); + StringScannerState state(scanner.state()); + auto name = readExpression(); + scanner.expectChar($colon); + return readSupportsDeclarationValue(name, start); + } + } + // EO readImportSupportsQuery + + // Consumes a function call within a `supports()` + // function after an `@import` if available. + SupportsFunction* StylesheetParser::tryImportSupportsFunction() { + if (!lookingAtInterpolatedIdentifier()) return nullptr; + + Offset start(scanner.offset); + StringScannerState state(scanner.state()); + auto name = readInterpolatedIdentifier(); + assert(name.asPlain != "not"); + + if (!scanner.scanChar($lparen)) { + scanner.backtrack(state); + return nullptr; + } + + auto value = readInterpolatedDeclarationValue(true, true); + scanner.expectChar($rparen); + + return new SupportsFunction( + scanner.relevantSpanFrom(start), + name, value); + } + // EO tryImportSupportsFunction + + // Consumes a supports condition and/or a media query after an `@import`. + std::pair StylesheetParser::tryImportQueries() + { + SupportsConditionObj supports; + if (scanIdentifier("supports")) { + scanner.expectChar($lparen); + Offset start(scanner.offset); + if (scanIdentifier("not")) { + scanWhitespace(); + SupportsCondition* condition = readSupportsConditionInParens(); + supports = SASS_MEMORY_NEW(SupportsNegation, + scanner.relevantSpanFrom(start), condition); + } + else if (scanner.peekChar() == $lparen) { + supports = readSupportsCondition(); + } + else { + Expression* name = readExpression(); + scanner.expectChar($colon); + scanWhitespace(); + Expression* value = readExpression(); + supports = SASS_MEMORY_NEW(SupportsDeclaration, + scanner.relevantSpanFrom(start), name, value); + } + scanner.expectChar($rparen); + scanWhitespace(); + } + + InterpolationObj media; + if (scanner.peekChar() == $lparen) { + media = readMediaQueryList(); + } + else if (lookingAtInterpolatedIdentifier()) { + media = readMediaQueryList(); + } + return std::make_pair(supports, media); + } + // EO tryImportQueries + + + // Consumes a `@use` rule. + // [start] should point before the `@`. + UseRule* StylesheetParser::readUseRule(Offset start) + { + + scanWhitespace(); + sass::string url(string()); + scanWhitespace(); + sass::string ns(readUseNamespace(url, start)); + scanWhitespace(); + + SourceSpan state(scanner.relevantSpanFrom(start)); + + // Check if name is valid identifier + //if (url.empty() || isDigit(url[0])) { + // // don't throw if it has an "as" + // callStackFrame csf(compiler, state); + // throw Exception::InvalidSassIdentifier(compiler, url); + //} + + sass::vector config; + bool hasWith(readWithConfiguration(config, false)); + expectStatementSeparator("@use rule"); + + if (isUseAllowed == false) { + callStackFrame csf(compiler, state); + throw Exception::TardyAtRule( + compiler, Strings::useRule); + } + + UseRuleObj rule = SASS_MEMORY_NEW(UseRule, + scanner.relevantSpanFrom(start), + scanner.sourceUrl, url, {}, + wconfig, std::move(config), hasWith); + + RAII_PTR(WithConfig, wconfig, rule); + + // Support internal modules first + if (startsWithIgnoreCase(url, "sass:", 5)) { + + if (hasWith) { + callStackFrame csf(compiler, rule->pstate()); + throw Exception::RuntimeException(compiler, + "Built-in modules can't be configured."); + } + + sass::string name(url.substr(5)); + if (ns.empty()) ns = name; + rule->ns(ns == "*" ? "" : ns); + + BuiltInMod* module(compiler.getModule(name)); + + if (module == nullptr) { + callStackFrame csf(compiler, rule->pstate()); + throw Exception::RuntimeException(compiler, + "Invalid internal module requested."); + } + + rule->module32(module); + + return rule.detach(); + } + // BuiltIn + + // Deduct the namespace from url + // After last slash before first dot + if (ns.empty() && !url.empty()) { + auto start = url.find_last_of("/\\"); + start = (start == NPOS ? 0 : start + 1); + auto end = url.find_first_of(".", start); + if (url[start] == '_') start += 1; + ns = url.substr(start, end); + } + + rule->ns(ns == "*" ? "" : ns); + return rule.detach(); + } + + // Consumes a `@forward` rule. + // [start] should point before the `@`. + ForwardRule* StylesheetParser::readForwardRule(Offset start) + { + scanWhitespace(); + sass::string url = string(); + + scanWhitespace(); + sass::string prefix; + if (scanIdentifier("as")) { + scanWhitespace(); + prefix = readIdentifier(); + scanner.expectChar($asterisk); + scanWhitespace(); + } + + bool isShown = false; + bool isHidden = false; + std::set varFilters; + std::set callFilters; + // Offset beforeShow(scanner.offset); + if (scanIdentifier("show")) { + readForwardMembers(varFilters, callFilters); + isShown = true; + } + else if (scanIdentifier("hide")) { + readForwardMembers(varFilters, callFilters); + isHidden = true; + } + + sass::vector config; + bool hasWith(readWithConfiguration(config, true)); + // RAII_FLAG(hasWithConfig, hasWithConfig || hasWith); + expectStatementSeparator("@forward rule"); + + if (isUseAllowed == false) { + SourceSpan state(scanner.relevantSpanFrom(start)); + callStackFrame csf(compiler, state); + throw Exception::ParserException(compiler, + "@forward rules must be written before any other rules."); + } + + ForwardRuleObj rule = SASS_MEMORY_NEW(ForwardRule, + scanner.relevantSpanFrom(start), + scanner.sourceUrl, url, {}, + prefix, wconfig, + std::move(varFilters), + std::move(callFilters), + std::move(config), + isShown, isHidden, hasWith); + + RAII_PTR(WithConfig, wconfig, rule); + + if (startsWithIgnoreCase(url, "sass:", 5)) { + + if (hasWith) { + callStackFrame csf(compiler, rule->pstate()); + throw Exception::RuntimeException(compiler, + "Built-in modules can't be configured."); + } + + sass::string name(url.substr(5)); + if (BuiltInMod* module = compiler.getModule(name)) { + rule->module32(module); + rule->root47(nullptr); + } + else { + callStackFrame csf(compiler, rule->pstate()); + throw Exception::RuntimeException(compiler, + "Invalid internal module requested."); + } + + } + + return rule.detach(); + } + + // Consumes an `@include` rule. + // [start] should point before the `@`. + IncludeRule* StylesheetParser::readIncludeRule(Offset start) + { + + sass::string ns; + sass::string name = readIdentifier(); + if (scanner.scanChar($dot)) { + ns = name; + name = readPublicIdentifier(); + } + + scanWhitespace(); + CallableArgumentsObj arguments; + if (scanner.peekChar() == $lparen) { + arguments = readArgumentInvocation(true); + } + scanWhitespace(); + + EnvFrame local(compiler, true); + + CallableSignatureObj contentArguments; + if (scanIdentifier("using")) { + scanWhitespace(); + contentArguments = parseArgumentDeclaration(); + scanWhitespace(); + } + + // ToDo: Add checks to allow to omit arguments fully + if (!arguments) { + SourceSpan pstate(scanner.relevantSpanFrom(start)); + arguments = SASS_MEMORY_NEW(CallableArguments, + std::move(pstate), {}, {}); + } + + sass::vector midxs; + + IncludeRuleObj rule = SASS_MEMORY_NEW(IncludeRule, + scanner.relevantSpanFrom(start), name, ns, arguments); + + ContentBlockObj content; + if (contentArguments || lookingAtChildren()) { + RAII_FLAG(inContentBlock, true); + // EnvFrame inner(compiler.varRoot.stack); + if (contentArguments.isNull()) { + // Dart-sass creates this one too + contentArguments = SASS_MEMORY_NEW( + CallableSignature, + scanner.relevantSpan()); + } + Offset start(scanner.offset); + rule->content(withChildren( + &StylesheetParser::readChildStatement, + start, contentArguments, local.idxs)); + } + else { + expectStatementSeparator(); + } + + /* + var span = + scanner.rawSpanFrom(start, start).expand((content ? ? arguments).span); + return IncludeRule(name, arguments, span, + namespace : ns, content : content); + */ + + return rule.detach(); // mixin.detach(); + } + // EO readIncludeRule + + // Consumes a `@media` rule. + // [start] should point before the `@`. + MediaRule* StylesheetParser::readMediaRule(Offset start) + { + // std::cerr << "read media rule\n"; + EnvFrame local(compiler, false); + InterpolationObj query = readMediaQueryList(); + return withChildren( + &StylesheetParser::readChildStatement, + start, query, local.idxs); + } + + + // Consumes a `@moz-document` rule. Gecko's `@-moz-document` diverges + // from [the specification][] allows the `url-prefix` and `domain` + // functions to omit quotation marks, contrary to the standard. + // [the specification]: http://www.w3.org/TR/css3-conditional/ + AtRule* StylesheetParser::readMozDocumentRule(Offset start, Interpolation* name) + { + + Offset valueStart(scanner.offset); + InterpolationBuffer buffer(scanner); + bool needsDeprecationWarning = false; + EnvFrame local(compiler, true); + + while (true) { + + if (scanner.peekChar() == $hash) { + buffer.add(readSingleInterpolation()); + needsDeprecationWarning = true; + } + else { + + + Offset identifierStart(scanner.offset); + sass::string identifier = this->readIdentifier(); + if (identifier == "url" || identifier == "url-prefix" || identifier == "domain") { + Interpolation* contents = tryUrlContents(identifierStart, /* name: */ identifier); + if (contents != nullptr) { + buffer.addInterpolation(contents); + } + else { + scanner.expectChar($lparen); + scanWhitespace(); + StringExpressionObj argument = readInterpolatedString(); + scanner.expectChar($rparen); + + buffer.write(identifier); + buffer.write($lparen); + buffer.addInterpolation(argument->getAsInterpolation()); + buffer.write($rparen); + } + + // A url-prefix with no argument, or with an empty string as an + // argument, is not (yet) deprecated. + sass::string trailing = buffer.trailingString(); + if (!endsWithIgnoreCase(trailing, "url-prefix()", 12) && + !endsWithIgnoreCase(trailing, "url-prefix('')", 14) && + !endsWithIgnoreCase(trailing, "url-prefix(\"\")", 14)) { + needsDeprecationWarning = true; + } + } + else if (identifier == "regexp") { + buffer.write("regexp("); + scanner.expectChar($lparen); + StringExpressionObj str = readInterpolatedString(); + buffer.addInterpolation(str->getAsInterpolation()); + scanner.expectChar($rparen); + buffer.write($rparen); + needsDeprecationWarning = true; + } + else { + error("Invalid function name.", + scanner.relevantSpanFrom(identifierStart)); + } + } + + scanWhitespace(); + if (!scanner.scanChar($comma)) break; + + buffer.write($comma); + buffer.write(rawText(&StylesheetParser::scanWhitespace)); + + } + + InterpolationObj value = buffer.getInterpolation(scanner.rawSpanFrom(valueStart)); + + + AtRule* atRule = withChildren( + &StylesheetParser::readChildStatement, + start, name, value, local.idxs, false); + + if (needsDeprecationWarning) { + + compiler.addDeprecation( + "@-moz-document is deprecated and support will be removed from Sass in a future\n" + "release. For details, see http://bit.ly/moz-document.", + atRule->pstate(), Logger::WARN_MOZ_DOC); + } + + return atRule; + + } + + // Consumes a `@return` rule. + // [start] should point before the `@`. + ReturnRule* StylesheetParser::readReturnRule(Offset start) + { + ExpressionObj value = readExpression(); + expectStatementSeparator("@return rule"); + return SASS_MEMORY_NEW(ReturnRule, + scanner.relevantSpanFrom(start), value); + } + // EO readReturnRule + + // Consumes a `@supports` rule. + // [start] should point before the `@`. + SupportsRule* StylesheetParser::readSupportsRule(Offset start) + { + auto condition = readSupportsCondition(); + scanWhitespace(); + EnvFrame local(compiler, true); + return withChildren( + &StylesheetParser::readChildStatement, + start, condition, local.idxs); + } + // EO readSupportsRule + + + // Consumes a `@debug` rule. + // [start] should point before the `@`. + DebugRule* StylesheetParser::readDebugRule(Offset start) + { + ExpressionObj value(readExpression()); + expectStatementSeparator("@debug rule"); + return SASS_MEMORY_NEW(DebugRule, + scanner.relevantSpanFrom(start), value); + } + // EO readDebugRule + + // Consumes a `@warn` rule. + // [start] should point before the `@`. + WarnRule* StylesheetParser::readWarnRule(Offset start) + { + ExpressionObj value(readExpression()); + expectStatementSeparator("@warn rule"); + return SASS_MEMORY_NEW(WarnRule, + scanner.relevantSpanFrom(start), value); + } + // EO readWarnRule + + // Consumes a `@while` rule. [start] should point before the `@`. [child] is called + // to consume any children that are specifically allowed in the caller's context. + WhileRule* StylesheetParser::readWhileRule(Offset start, Statement* (StylesheetParser::* child)()) + { + RAII_FLAG(inControlDirective, true); + EnvFrame local(compiler, true); + ExpressionObj condition(readExpression()); + return withChildren(child, + start, condition.ptr(), local.idxs); + } + // EO readWhileRule + + // Consumes an at-rule that's not explicitly supported by Sass. + // [start] should point before the `@`. [name] is the name of the at-rule. + AtRule* StylesheetParser::readAnyAtRule(Offset start, Interpolation* name) + { + RAII_FLAG(inUnknownAtRule, true); + EnvFrame local(compiler, false); + + InterpolationObj value; + uint8_t next = scanner.peekChar(); + if (next != $exclamation && !atEndOfStatement()) { + value = readAlmostAnyValue(); + } + + if (lookingAtChildren()) { + return withChildren( + &StylesheetParser::readChildStatement, + start, name, value, local.idxs, false); + } + expectStatementSeparator(); + return SASS_MEMORY_NEW(AtRule, + scanner.relevantSpanFrom(start), + name, value, local.idxs, true); + } + // EO readAnyAtRule + + // Parse almost any value to report disallowed at-rule + Statement* StylesheetParser::throwDisallowedAtRule(Offset start) + { + InterpolationObj value(readAlmostAnyValue()); + error("This at-rule is not allowed here.", + scanner.relevantSpanFrom(start)); + return nullptr; + } + // EO throwDisallowedAtRule + + // Argument declaration is tricky in terms of scoping. + // The variable before the colon is defined on the new frame. + // But the right side is evaluated in the parent scope. + CallableSignature* StylesheetParser::parseArgumentDeclaration() + { + + Offset start(scanner.offset); + scanner.expectChar($lparen); + scanWhitespace(); + sass::vector arguments; + EnvKeySet named; + sass::string restArgument; + while (scanner.peekChar() == $dollar) { + Offset variableStart(scanner.offset); + sass::string name(variableName()); + EnvKey norm(name); + scanWhitespace(); + + ExpressionObj defaultValue; + if (scanner.scanChar($colon)) { + scanWhitespace(); + defaultValue = readExpressionUntilComma(); + } + else if (scanner.scanChar($dot)) { + scanner.expectChar($dot); + scanner.expectChar($dot); + scanWhitespace(); + restArgument = name; + // Defer adding variable until we parsed expression + // Just in case the same variable is mentioned again + compiler.varRoot.stack.back()->createVariable(norm); + break; + } + + // Defer adding variable until we parsed expression + // Just in case the same variable is mentioned again + compiler.varRoot.stack.back()->createVariable(norm); + + arguments.emplace_back(SASS_MEMORY_NEW(Argument, + scanner.relevantSpanFrom(variableStart), name, defaultValue)); + + if (named.count(norm) == 1) { + error("Duplicate argument.", + arguments.back()->pstate()); + } + named.insert(std::move(norm)); + + if (!scanner.scanChar($comma)) break; + scanWhitespace(); + } + scanner.expectChar($rparen); + + return SASS_MEMORY_NEW( + CallableSignature, + scanner.relevantSpanFrom(start), + std::move(arguments), + std::move(restArgument)); + + } + // EO parseArgumentDeclaration + + // Consumes an argument invocation. If [mixin] is `true`, this is parsed + // as a mixin invocation. Mixin invocations don't allow the Microsoft-style + // `=` operator at the top level, but function invocations do. + CallableArguments* StylesheetParser::readArgumentInvocation( + bool mixin, bool allowEmptySecondArg) + { + + Offset start(scanner.offset); + scanner.expectChar($lparen); + scanWhitespace(); + + ExpressionVector positional; + // Maybe make also optional? + ExpressionFlatMap named; + ExpressionObj restArg; + ExpressionObj kwdRest; + while (lookingAtExpression()) { + Offset start(scanner.offset); + ExpressionObj expression = readExpressionUntilComma(!mixin); + if (expression == nullptr) { + error("Expected expression.", + scanner.rawSpan()); + } + scanWhitespace(); + VariableExpression* var = expression->isaVariableExpression(); + if (var && scanner.scanChar($colon)) { + scanWhitespace(); + if (named.count(var->name()) == 1) { + error("Duplicate argument.", + expression->pstate()); + } + auto ex = readExpressionUntilComma(!mixin); + named[var->name()] = ex; + } + else if (scanner.scanChar($dot)) { + scanner.expectChar($dot); + scanner.expectChar($dot); + if (restArg == nullptr) { + restArg = expression; + } + else { + kwdRest = expression; + scanWhitespace(); + break; + } + } + else if (!named.empty()) { + // Positional before + if (!scanner.scan("...")) { + error("Positional arguments must" + " come before keyword arguments.", + scanner.spanAt(start)); + } + } + else { + positional.emplace_back(expression); + } + + scanWhitespace(); + if (!scanner.scanChar($comma)) break; + scanWhitespace(); + + if (allowEmptySecondArg && + positional.size() == 1 && + named.empty() && + restArg == nullptr && + scanner.peekChar() == $rparen) + { + positional.push_back( + SASS_MEMORY_NEW(StringExpression, scanner.rawSpan(), "")); + break; + } + + } + scanner.expectChar($rparen); + + return SASS_MEMORY_NEW( + CallableArguments, + scanner.relevantSpanFrom(start), + std::move(positional), + std::move(named), + restArg, kwdRest); + + } + // EO readArgumentInvocation + + // Consumes an expression. If [bracketList] is true, parses this expression as + // the contents of a bracketed list. If [singleEquals] is true, allows the + // Microsoft-style `=` operator at the top level. If [until] is passed, it's + // called each time the expression could end and still be a valid expression. + // When it returns `true`, this returns the expression. + Expression* StylesheetParser::readExpression( + bool bracketList, bool singleEquals, + bool(StylesheetParser::* until)()) + { + + NESTING_GUARD(recursion); + + // std::cerr << "---------- PARSE EXPRESSION\n"; + + if (until != nullptr && (this->*until)()) { + SourceSpan span(scanner.rawSpan()); + error("Expected expression.", span); + } + + // StringScannerState beforeBracket; + Offset start(scanner.offset); + if (bracketList) { + // beforeBracket = scanner.position; + scanner.expectChar($lbracket); + scanWhitespace(); + + if (scanner.scanChar($rbracket)) { + ListExpression* list = SASS_MEMORY_NEW(ListExpression, + scanner.relevantSpanFrom(start), SASS_UNDEF); + list->hasBrackets(true); + return list; + } + } + + ExpressionParser ep(*this); + + bool wasInParentheses = inParentheses; + + while (true) { + scanWhitespace(); + if (until != nullptr && (this->*until)()) break; + + uint8_t next, first = scanner.peekChar(); + Offset beforeToken(scanner.offset); + + switch (first) { + case $lparen: + // Parenthesized numbers can't be slash-separated. + ep.addSingleExpression(readParenthesizedExpression()); + break; + + case $lbracket: + ep.addSingleExpression(readExpression(true)); + break; + + case $dollar: + ep.addSingleExpression(readVariableExpression()); + break; + + case $ampersand: + ep.addSingleExpression(readParentExpression()); + break; + + case $apos: + case $quote: + ep.addSingleExpression(readInterpolatedString()); + break; + + case $hash: + ep.addSingleExpression(readHashExpression()); + break; + + case $equal: + scanner.readChar(); + if (singleEquals && scanner.peekChar() != $equal) { + ep.resolveSpaceExpressions(); + ep.singleEqualsOperand = ep.singleExpression; + ep.singleExpression = {}; + } + else { + scanner.expectChar($equal); + ep.addOperator(SassOperator::EQ, beforeToken); + } + break; + + case $exclamation: + next = scanner.peekChar(1); + if (next == $equal) { + scanner.readChar(); + scanner.readChar(); + ep.addOperator(SassOperator::NEQ, beforeToken); + } + else if (next == $nul || + equalsLetterIgnoreCase($i, next) || + isWhitespace(next)) + { + ep.addSingleExpression(readImportantExpression()); + } + else { + goto endOfLoop; + } + break; + + case $lt: + scanner.readChar(); + ep.addOperator(scanner.scanChar($equal) + ? SassOperator::LTE : SassOperator::LT, beforeToken); + break; + + case $gt: + scanner.readChar(); + ep.addOperator(scanner.scanChar($equal) + ? SassOperator::GTE : SassOperator::GT, beforeToken); + break; + + case $asterisk: + scanner.readChar(); + ep.addOperator(SassOperator::MUL, beforeToken); + break; + + case $plus: + if (ep.singleExpression == nullptr) { + ep.addSingleExpression(readUnaryOpExpression()); + } + else { + scanner.readChar(); + ep.addOperator(SassOperator::ADD, beforeToken); + } + break; + + case $minus: + next = scanner.peekChar(1); + if ((isDigit(next) || next == $dot) && + // Make sure `1-2` parses as `1 - 2`, not `1 (-2)`. + (ep.singleExpression == nullptr || + isWhitespace(scanner.peekChar(-1)))) { + ep.addSingleExpression(readNumberExpression(), true); + } + else if (lookingAtInterpolatedIdentifier()) { + ep.addSingleExpression(readIdentifierLike()); + } + else if (ep.singleExpression == nullptr) { + ep.addSingleExpression(readUnaryOpExpression()); + } + else { + scanner.readChar(); + ep.addOperator(SassOperator::SUB, beforeToken); + } + break; + + case $slash: + if (ep.singleExpression == nullptr) { + ep.addSingleExpression(readUnaryOpExpression()); + } + else { + scanner.readChar(); + ep.addOperator(SassOperator::DIV, beforeToken); + } + break; + + case $percent: + scanner.readChar(); + ep.addOperator(SassOperator::MOD, beforeToken); + break; + + case $0: + case $1: + case $2: + case $3: + case $4: + case $5: + case $6: + case $7: + case $8: + case $9: + ep.addSingleExpression(readNumberExpression(), true); + break; + + case $dot: + if (scanner.peekChar(1) == $dot) goto endOfLoop; + ep.addSingleExpression(readNumberExpression(), true); + break; + + case $a: + if (!plainCss() && scanIdentifier("and")) { + ep.addOperator(SassOperator::AND, beforeToken); + } + else { + ep.addSingleExpression(readIdentifierLike()); + } + break; + + case $o: + if (!plainCss() && scanIdentifier("or")) { + ep.addOperator(SassOperator::OR, beforeToken); + } + else { + ep.addSingleExpression(readIdentifierLike()); + } + break; + + case $u: + case $U: + if (scanner.peekChar(1) == $plus) { + ep.addSingleExpression(readUnicodeRange()); + } + else { + ep.addSingleExpression(readIdentifierLike()); + } + break; + + case $b: + case $c: + case $d: + case $e: + case $f: + case $g: + case $h: + case $i: + case $j: + case $k: + case $l: + case $m: + case $n: + case $p: + case $q: + case $r: + case $s: + case $t: + case $v: + case $w: + case $x: + case $y: + case $z: + case $A: + case $B: + case $C: + case $D: + case $E: + case $F: + case $G: + case $H: + case $I: + case $J: + case $K: + case $L: + case $M: + case $N: + case $O: + case $P: + case $Q: + case $R: + case $S: + case $T: + case $V: + case $W: + case $X: + case $Y: + case $Z: + case $_: + case $backslash: + ep.addSingleExpression(readIdentifierLike()); + break; + + case $comma: + // If we discover we're parsing a list whose first element is a + // division operation, and we're in parentheses, re-parse outside of a + // parent context. This ensures that `(1/2, 1)` doesn't perform division + // on its first element. + if (inParentheses) { + inParentheses = false; + if (ep.allowSlash) { + ep.resetState(); + break; + } + } + + if (ep.singleExpression == nullptr) { + SourceSpan span(scanner.rawSpan()); + error("Expected expression.", span); + } + + ep.resolveSpaceExpressions(); + ep.commaExpressions.emplace_back(ep.singleExpression); + scanner.readChar(); + ep.allowSlash = true; + ep.singleExpression = {}; + break; + + default: + if (first != $nul && first >= 0x80) { + ep.addSingleExpression(readIdentifierLike()); + break; + } + else { + goto endOfLoop; + } + } + } + + endOfLoop: + + if (bracketList) scanner.expectChar($rbracket); + if (!ep.commaExpressions.empty()) { + ep.resolveSpaceExpressions(); + inParentheses = wasInParentheses; + if (ep.singleExpression != nullptr) { + ep.commaExpressions.emplace_back(ep.singleExpression); + } + ListExpression* list = SASS_MEMORY_NEW(ListExpression, + scanner.relevantSpanFrom(start), SASS_COMMA); + list->concat(std::move(ep.commaExpressions)); + list->hasBrackets(bracketList); + //debug_ast(list); + return list; + } + else if (bracketList && + !ep.spaceExpressions.empty() && + ep.singleEqualsOperand == nullptr) { + ep.resolveOperations(); + ListExpression* list = SASS_MEMORY_NEW(ListExpression, + scanner.relevantSpanFrom(start), SASS_SPACE); + ep.spaceExpressions.emplace_back(ep.singleExpression); + list->concat(std::move(ep.spaceExpressions)); + list->hasBrackets(true); + //debug_ast(list); + return list; + } + else { + ep.resolveSpaceExpressions(); + if (bracketList) { + ListExpression* list = SASS_MEMORY_NEW(ListExpression, + scanner.relevantSpanFrom(start), SASS_UNDEF); + list->append(ep.singleExpression); + list->hasBrackets(true); + //debug_ast(list); + return list; + } + //debug_ast(ep.singleExpression); + return ep.singleExpression.detach(); + } + + } + // EO expression + + // Returns `true` if scanner reached a `,` + bool StylesheetParser::lookingAtComma() + { + return scanner.peekChar() == $comma; + } + + + // Consumes an expression until it reaches a top-level comma. + // If [singleEquals] is true, this will allow the + // Microsoft-style `=` operator at the top level. + Expression* StylesheetParser::readExpressionUntilComma(bool singleEquals) + { + return readExpression(false, singleEquals, + &StylesheetParser::lookingAtComma); + } + + + // Consumes an expression until it reaches a top-level comma. + // If [singleEquals] is true, this will allow the + // Microsoft-style `=` operator at the top level. + Expression* StylesheetParser::readSingleExpression() + { + NESTING_GUARD(recursion); + uint8_t first = scanner.peekChar(); + switch (first) { + // Note: when adding a new case, make sure it's reflected in + // [lookingAtExpression] and [_expression]. + case $lparen: + return readParenthesizedExpression(); + case $slash: + return readUnaryOpExpression(); + case $dot: + return readNumberExpression(); + case $lbracket: + return readExpression(true); + case $dollar: + return readVariableExpression(); + case $ampersand: + return readParentExpression(); + + case $apos: + case $quote: + return readInterpolatedString(); + + case $hash: + return readHashExpression(); + + case $plus: + return readPlusExpression(); + + case $minus: + return readMinusExpression(); + + case $exclamation: + return readImportantExpression(); + + case $u: + case $U: + if (scanner.peekChar(1) == $plus) { + return readUnicodeRange(); + } + else { + return readIdentifierLike(); + } + break; + + case $0: + case $1: + case $2: + case $3: + case $4: + case $5: + case $6: + case $7: + case $8: + case $9: + return readNumberExpression(); + break; + + case $a: + case $b: + case $c: + case $d: + case $e: + case $f: + case $g: + case $h: + case $i: + case $j: + case $k: + case $l: + case $m: + case $n: + case $o: + case $p: + case $q: + case $r: + case $s: + case $t: + case $v: + case $w: + case $x: + case $y: + case $z: + case $A: + case $B: + case $C: + case $D: + case $E: + case $F: + case $G: + case $H: + case $I: + case $J: + case $K: + case $L: + case $M: + case $N: + case $O: + case $P: + case $Q: + case $R: + case $S: + case $T: + case $V: + case $W: + case $X: + case $Y: + case $Z: + case $_: + case $backslash: + return readIdentifierLike(); + break; + + default: + if (first != $nul && first >= 0x80) { + return readIdentifierLike(); + } + error("Expected expression.", + scanner.rawSpan()); + return nullptr; + } + } + // EO readSingleExpression + + // Consumes a parenthesized expression. + Expression* StylesheetParser::readParenthesizedExpression() + { + // Expressions are only allowed within calculations, but we verify this at + // evaluation time. + if (plainCss()) { + // This one is needed ... + // error("Parentheses aren't allowed in plain CSS outside calculation.", + //error("Parentheses aren't allowed in plain CSS.", + // scanner.rawSpan()); + } + + RAII_FLAG(inParentheses, true); + + Offset start(scanner.offset); + scanner.expectChar($lparen); + scanWhitespace(); + if (!lookingAtExpression()) { + scanner.expectChar($rparen); + return SASS_MEMORY_NEW(ListExpression, + scanner.relevantSpanFrom(start), + SASS_UNDEF); + } + + ExpressionObj first = readExpressionUntilComma(); + if (scanner.scanChar($colon)) { + scanWhitespace(); + return readMapExpression(first, start); + } + + if (!scanner.scanChar($comma)) { + scanner.expectChar($rparen); + return SASS_MEMORY_NEW(ParenthesizedExpression, + scanner.relevantSpanFrom(start), first); + } + scanWhitespace(); + + ExpressionVector + expressions = { first }; + + ListExpressionObj list = SASS_MEMORY_NEW(ListExpression, + scanner.relevantSpanFrom(start), SASS_COMMA); + + while (true) { + if (!lookingAtExpression()) { + break; + } + expressions.emplace_back(readExpressionUntilComma()); + if (!scanner.scanChar($comma)) { + break; + } + list->separator(SASS_COMMA); + scanWhitespace(); + } + + scanner.expectChar($rparen); + list->concat(std::move(expressions)); + list->pstate(scanner.relevantSpanFrom(start)); + return list.detach(); + } + // EO readParenthesizedExpression + + // Consumes a map expression. This expects to be called after the + // first colon in the map, with [first] as the expression before + // the colon and [start] the point before the opening parenthesis. + Expression* StylesheetParser::readMapExpression(Expression* first, Offset start) + { + MapExpressionObj map = SASS_MEMORY_NEW( + MapExpression, scanner.relevantSpanFrom(start)); + + map->append(first); + map->append(readExpressionUntilComma()); + + while (scanner.scanChar($comma)) { + scanWhitespace(); + if (!lookingAtExpression()) break; + + map->append(readExpressionUntilComma()); + scanner.expectChar($colon); + scanWhitespace(); + map->append(readExpressionUntilComma()); + } + + scanner.expectChar($rparen); + map->pstate(scanner.relevantSpanFrom(start)); + return map.detach(); + } + // EO _map + + // Consumes an expression that starts with a `#`. + Expression* StylesheetParser::readHashExpression() + { + // assert(scanner.peekChar() == $hash); + if (scanner.peekChar(1) == $lbrace) { + return readIdentifierLike(); + } + + Offset start(scanner.offset); + StringScannerState state(scanner.state()); + scanner.expectChar($hash); + + uint8_t first = scanner.peekChar(); + if (first != $nul && isDigit(first)) { + // ColorExpression + return readColorExpression(state); + } + + StringScannerState afterHash = scanner.state(); + InterpolationObj identifier = readInterpolatedIdentifier(); + if (isHexColor(identifier)) { + scanner.backtrack(afterHash); + return readColorExpression(state); + } + + InterpolationBuffer buffer(scanner); + buffer.write($hash); + buffer.addInterpolation(identifier); + SourceSpan pstate(scanner.relevantSpanFrom(start)); + return SASS_MEMORY_NEW(StringExpression, + pstate, buffer.getInterpolation(pstate)); + } + + ColorExpression* StylesheetParser::readColorExpression(StringScannerState state) + { + + uint8_t digit1 = readHexDigit(); + uint8_t digit2 = readHexDigit(); + uint8_t digit3 = readHexDigit(); + + uint8_t red; + uint8_t green; + uint8_t blue; + double alpha = 1.0; + bool keep = true; + + if (!isHex(scanner.peekChar())) { + red = (digit1 << 4) + digit1; + green = (digit2 << 4) + digit2; + blue = (digit3 << 4) + digit3; + } + else { + uint8_t digit4 = readHexDigit(); + if (!isHex(scanner.peekChar())) { + red = (digit1 << 4) + digit1; + green = (digit2 << 4) + digit2; + blue = (digit3 << 4) + digit3; + uint8_t a = (digit4 << 4) + digit4; + alpha = a / 255.0; + keep = false; + } + else { + red = (digit1 << 4) + digit2; + green = (digit3 << 4) + digit4; + uint8_t digit5 = readHexDigit(); + uint8_t digit6 = readHexDigit(); + blue = (digit5 << 4) + digit6; + if (isHex(scanner.peekChar())) { + uint8_t digit7 = readHexDigit(); + uint8_t digit8 = readHexDigit(); + uint8_t a = (digit7 << 4) + digit8; + alpha = a / 255.0; + keep = false; + } + } + } + + SourceSpan pstate(scanner.relevantSpanFrom(state.offset)); + sass::string original(state.position, scanner.position); + if (keep == false) original = str_empty; // reset!? + Color* color = SASS_MEMORY_NEW(ColorRgba, pstate, + red, green, blue, alpha, original, false); + return SASS_MEMORY_NEW(ColorExpression, pstate, color); + } + + // Returns whether [interpolation] is a plain + // string that can be parsed as a hex color. + bool StylesheetParser::isHexColor(Interpolation* interpolation) const + { + const sass::string& plain(interpolation->getPlainString()); + if (plain.empty()) return false; + if (plain.length() != 3 && + plain.length() != 4 && + plain.length() != 6 && + plain.length() != 8) + { + return false; + } + // return plain.codeUnits.every(isHex); + for (size_t i = 0; i < plain.length(); i++) { + if (!isHex(plain[i])) return false; + } + return true; + } + // EO isHexColor + + // Consumes a single hexadecimal digit. + uint8_t StylesheetParser::readHexDigit() + { + uint8_t chr = scanner.peekChar(); + if (chr == $nul || !isHex(chr)) { + error("Expected hex digit.", + scanner.relevantSpan()); + } + return asHex(scanner.readChar()); + } + // EO readHexDigit + + // Consumes an expression that starts with a `+`. + Expression* StylesheetParser::readPlusExpression() + { + SASS_ASSERT(scanner.peekChar() == $plus, + "plusExpression expects a plus sign"); + uint8_t next = scanner.peekChar(1); + if (isDigit(next) || next == $dot) { + return readNumberExpression(); + } + else { + return readUnaryOpExpression(); + } + } + // EO readPlusExpression + + // Consumes an expression that starts with a `-`. + Expression* StylesheetParser::readMinusExpression() + { + SASS_ASSERT(scanner.peekChar() == $minus, + "minusExpression expects a minus sign"); + uint8_t next = scanner.peekChar(1); + if (isDigit(next) || next == $dot) return readNumberExpression(); + if (lookingAtInterpolatedIdentifier()) return readIdentifierLike(); + return readUnaryOpExpression(); + } + // EO readMinusExpression + + // Consumes an `!important` expression. + StringExpression* StylesheetParser::readImportantExpression() + { + SASS_ASSERT(scanner.peekChar() == $exclamation, + "importantExpression expects an exclamation"); + Offset start(scanner.offset); + scanner.readChar(); + scanWhitespace(); + expectIdentifier("important", "\"important\""); + return SASS_MEMORY_NEW(StringExpression, + scanner.relevantSpanFrom(start), + sass::string("!important")); + } + // EO readImportantExpression + + // Consumes a unary operation expression. + UnaryOpExpression* StylesheetParser::readUnaryOpExpression() + { + Offset start(scanner.offset); + UnaryOpType op = + UnaryOpType::PLUS; + switch (scanner.readChar()) { + case $plus: + op = UnaryOpType::PLUS; + break; + case $minus: + op = UnaryOpType::MINUS; + break; + case $slash: + op = UnaryOpType::SLASH; + break; + default: + error("Expected unary operator.", + scanner.relevantSpan()); + } + + if (plainCss() && op != UnaryOpType::SLASH) { + error("Operators aren't allowed in plain CSS.", + scanner.relevantSpan()); + } + + scanWhitespace(); + Expression* operand = readSingleExpression(); + return SASS_MEMORY_NEW(UnaryOpExpression, + scanner.relevantSpanFrom(start), + op, operand); + } + // EO readUnaryOpExpression + + // Consumes a number expression. + NumberExpression* StylesheetParser::readNumberExpression() + { + StringScannerState start = scanner.state(); + uint8_t first = scanner.peekChar(); + + double sign = first == $minus ? -1 : 1; + if (first == $plus || first == $minus) scanner.readChar(); + + double number = scanner.peekChar() == $dot ? 0.0 : naturalNumber(); + + // Don't complain about a dot after a number unless the number + // starts with a dot. We don't allow a plain ".", but we need + // to allow "1." so that "1..." will work as a rest argument. + number += tryDecimal(scanner.position != start.position); + number *= tryExponent(); + + sass::string unit; + if (scanner.scanChar($percent)) { + unit = "%"; + } + else if (lookingAtIdentifier() && + // Disallow units beginning with `--`. + (scanner.peekChar() != $minus || scanner.peekChar(1) != $minus)) { + unit = readIdentifier(true); + } + + auto pstate(scanner.relevantSpanFrom(start.offset)); + return SASS_MEMORY_NEW(NumberExpression, pstate, + SASS_MEMORY_NEW(Number, pstate, sign * number, unit)); + } + + /* Locale unspecific atof function. */ + double sass_strtod(const char* str) + { + char separator = *(localeconv()->decimal_point); + if (separator != '.') { + // The current locale specifies another + // separator. convert the separator to the + // one understood by the locale if needed + const char* found = strchr(str, '.'); + if (found != NULL) { + // substitution is required. perform the substitution on a exposing + // of the string. This is slower but it is thread safe. + char* copy = sass_copy_c_string(str); + *(copy + (found - str)) = separator; + double res = strtod(copy, NULL); + free(copy); + return res; + } + } + + return strtod(str, NULL); + } + + // Consumes the decimal component of a number and returns its value, or 0 + // if there is no decimal component. If [allowTrailingDot] is `false`, this + // will throw an error if there's a dot without any numbers following it. + // Otherwise, it will ignore the dot without consuming it. + double StylesheetParser::tryDecimal(bool allowTrailingDot) + { + // Offset start(scanner.offset); + StringScannerState state(scanner.state()); + if (scanner.peekChar() != $dot) return 0.0; + + if (!isDigit(scanner.peekChar(1))) { + if (allowTrailingDot) return 0.0; + scanner.consumedChar($dot); + error("Expected digit.", + scanner.rawSpan()); + } + + scanner.readChar(); + while (isDigit(scanner.peekChar())) { + scanner.readChar(); + } + + // Use built-in double parsing so that we don't accumulate + // floating-point errors for numbers with lots of digits. + sass::string nr(scanner.substring(state.position)); + return sass_strtod(nr.c_str()); + } + // EO tryDecimal + + // Consumes the exponent component of a number and returns + // its value, or 1 if there is no exponent component. + double StylesheetParser::tryExponent() + { + uint8_t first = scanner.peekChar(); + if (first != $e && first != $E) return 1.0; + + uint8_t next = scanner.peekChar(1); + if (!isDigit(next) && next != $minus && next != $plus) return 1.0; + + scanner.readChar(); + double exponentSign = next == $minus ? -1.0 : 1.0; + if (next == $plus || next == $minus) scanner.readChar(); + if (!isDigit(scanner.peekChar())) { + SourceSpan span(scanner.relevantSpan()); + callStackFrame frame(compiler, + BackTrace(span)); + error( + "Expected digit.", + scanner.relevantSpan()); + } + + double exponent = 0.0; + while (isDigit(scanner.peekChar())) { + exponent *= 10.0; + exponent += scanner.readChar() - $0; + } + + return pow(10.0, exponentSign * exponent); + } + // EO tryExponent + + // Consumes a unicode range expression. + StringExpression* StylesheetParser::readUnicodeRange() + { + + + StringScannerState state = scanner.state(); + expectIdentChar($u); + scanner.expectChar($plus); + + size_t firstRangeLength = 0; + while (scanCharIf(isHex)) { + firstRangeLength++; + } + + bool hasQuestionMark = false; + while (scanner.scanChar($question)) { + hasQuestionMark = true; + firstRangeLength++; + } + + if (firstRangeLength == 0) { + error("Expected hex digit or \"?\".", scanner.rawSpan()); + } + else if (firstRangeLength > 6) { + error("Expected at most 6 digits.", scanner.rawSpanFrom(state.offset)); + } + else if (hasQuestionMark) { + return SASS_MEMORY_NEW(StringExpression, + scanner.rawSpanFrom(state.offset), + scanner.substring(state.position)); + } + + if (scanner.scanChar($minus)) { + auto secondRangeStart = scanner.state(); + size_t secondRangeLength = 0; + while (scanCharIf(isHex)) { + secondRangeLength++; + } + + if (secondRangeLength == 0) { + error("Expected hex digit.", scanner.rawSpan()); + } + else if (secondRangeLength > 6) { + error("Expected at most 6 digits.", scanner.rawSpanFrom(secondRangeStart.offset)); + } + } + + if (lookingAtInterpolatedIdentifierBody()) { + error("Expected end of identifier.", + scanner.relevantSpan()); + } + + return SASS_MEMORY_NEW(StringExpression, + scanner.relevantSpanFrom(state.offset), + scanner.substring(state.position)); + } + // EO readUnicodeRange + + // Consumes a variable expression (only called without namespace). + VariableExpression* StylesheetParser::readVariableExpression(bool hoist) + { + Offset start(scanner.offset); + + sass::string ns, name = variableName(); + if (scanner.peekChar() == $dot && scanner.peekChar(1) != $dot) { + // Skip the dot + scanner.readChar(); + ns = name; + name = readPublicIdentifier(); + } + + if (plainCss()) { + error("Sass variables aren't allowed in plain CSS.", + scanner.relevantSpanFrom(start)); + } + + if (!ns.empty()) { + auto pstate(scanner.relevantSpanFrom(start)); + callStackFrame csf(compiler, pstate); + throw Exception::ParserException(compiler, + "Variable namespaces not supported!"); + } + + VariableExpression* expression = + SASS_MEMORY_NEW(VariableExpression, + scanner.relevantSpanFrom(start), + name, ns); + return expression; + + } + // readVariableExpression + + // Consumes a selector expression. + SelectorExpression* StylesheetParser::readParentExpression() + { + if (plainCss()) { + error("The parent selector isn't allowed in plain CSS.", + scanner.rawSpan()); + /* ,length: 1 */ + } + + Offset start(scanner.offset); + scanner.expectChar($ampersand); + + if (scanner.scanChar($ampersand)) { + compiler.addWarning( + "In Sass, \"&&\" means two copies of the parent selector. You " + "probably want to use \"and\" instead.", + scanner.relevantSpanFrom(start), + Logger::WARN_DOUBLE_PARENT); + scanner.offset.column -= 1; + scanner.position -= 1; + } + + return SASS_MEMORY_NEW(SelectorExpression, + scanner.relevantSpanFrom(start)); + } + // readParentExpression + + // Consumes a quoted string expression. + StringExpression* StylesheetParser::readInterpolatedString() + { + // NOTE: this logic is largely duplicated in ScssParser.readInterpolatedString. + // Most changes here should be mirrored there. + + Offset start(scanner.offset); + uint8_t quote = scanner.readChar(); + uint8_t next = 0, second = 0; + + if (quote != $apos && quote != $quote) { + error("Expected string.", + scanner.relevantSpanFrom(start)); + } + + InterpolationBuffer buffer(scanner); + while (true) { + if (!scanner.peekChar(next)) { + break; + } + if (next == quote) { + scanner.readChar(); + break; + } + else if (next == $nul || isNewline(next)) { + sass::sstream strm; + strm << "Expected " << quote << "."; + error(strm.str(), + scanner.relevantSpan()); + } + else if (next == $backslash) { + if (!scanner.peekChar(second, 1)) { + break; + } + if (isNewline(second)) { + scanner.readChar(); + scanner.readChar(); + if (second == $cr) scanner.scanChar($lf); + } + else { + buffer.writeCharCode(escapeCharacter()); + } + } + else if (next == $hash) { + if (scanner.peekChar(1) == $lbrace) { + buffer.add(readSingleInterpolation()); + } + else { + buffer.write(scanner.readChar()); + } + } + else { + buffer.write(scanner.readChar()); + } + } + + SourceSpan pstate(scanner.relevantSpanFrom(start)); + Interpolation* itpl(buffer.getInterpolation(pstate)); + return SASS_MEMORY_NEW(StringExpression, pstate, itpl, true); + } + // EO readInterpolatedString + + // Consumes an expression that starts like an identifier. + Expression* StylesheetParser::readIdentifierLike() + { + Offset start(scanner.offset); + InterpolationObj identifier = readInterpolatedIdentifier(); + sass::string plain(identifier->getPlainString()); + // std::cerr << "readIdentifierLike " << plain << "\n"; + + if (!plain.empty()) { + if (plain == "if" && scanner.peekChar() == $lparen) { + CallableArguments* invocation = readArgumentInvocation(); + return SASS_MEMORY_NEW(IfExpression, + invocation->pstate(), invocation); + } + else if (plain == "not") { + scanWhitespace(); + Expression* expression = readSingleExpression(); + return SASS_MEMORY_NEW(UnaryOpExpression, + scanner.relevantSpanFrom(start), + UnaryOpType::NOT, + expression); + } + + if (scanner.peekChar() != $lparen) { + if (plain == "false") { + auto pstate(scanner.relevantSpanFrom(start)); + return SASS_MEMORY_NEW(BooleanExpression, pstate, + SASS_MEMORY_NEW(Boolean, pstate, false)); + } + else if (plain == "true") { + auto pstate(scanner.relevantSpanFrom(start)); + return SASS_MEMORY_NEW(BooleanExpression, pstate, + SASS_MEMORY_NEW(Boolean, pstate, true)); + } + else if (plain == "null") { + auto pstate(scanner.relevantSpanFrom(start)); + return SASS_MEMORY_NEW(NullExpression, pstate, + SASS_MEMORY_NEW(Null, pstate)); + } + + if (const ColorRgba* color = name_to_color(plain)) { + // ToDo: can we avoid this copy here? + ColorRgba* copy = SASS_MEMORY_COPY(color); + copy->pstate(identifier->pstate()); + copy->disp(plain); + return SASS_MEMORY_NEW(ColorExpression, + copy->pstate(), copy); + } + } + + auto specialFunction = trySpecialFunction(plain, start); + if (specialFunction != nullptr) { + // std::cerr << "is special function\n"; + return specialFunction; + } + } + + sass::string ns; + Offset beforeName(scanner.offset); + uint8_t next = scanner.peekChar(); + // std::cerr << "next char is " << next << "\n"; + if (next == $dot) { + + if (scanner.peekChar(1) == $dot) { + return SASS_MEMORY_NEW(StringExpression, + scanner.relevantSpanFrom(beforeName), identifier); + } + scanner.readChar(); + + if (scanner.peekChar() == $dollar) { + sass::string name(variableName()); + + sass::vector vidxs; + + VariableExpressionObj expression = SASS_MEMORY_NEW(VariableExpression, + scanner.relevantSpanFrom(start), name, plain); + + if (isPrivate(name)) { + callStackFrame csf(compiler, expression->pstate()); + throw Exception::ParserException(compiler, + "Private members can't be accessed " + "from outside their modules."); + } + + return expression.detach(); + } + + ns = identifier->getPlainString(); + beforeName = scanner.offset; + + Offset before(scanner.offset); + StringObj ident(SASS_MEMORY_NEW(String, + scanner.relevantSpanFrom(before), + readPublicIdentifier())); + + InterpolationObj itpl = SASS_MEMORY_NEW(Interpolation, + ident->pstate(), ident); + + if (ns.empty()) { + error("Interpolation isn't allowed in namespaces.", + scanner.relevantSpanFrom(start)); + } + + CallableArguments* args = readArgumentInvocation(); + sass::string name(identifier->getPlainString()); + + // Plain Css as it's interpolated + if (identifier->getPlainString().empty()) { + return SASS_MEMORY_NEW(ItplFnExpression, + scanner.relevantSpanFrom(start), itpl, args, ns); + } + + return SASS_MEMORY_NEW(FunctionExpression, + scanner.relevantSpanFrom(start), + itpl->getPlainString(), + args, name); + } + else if (next == $lparen) { + + // Plain Css as it's interpolated + if (identifier->getPlainString().empty()) { + // std::cerr << "Create a itpl fn expression\n"; + CallableArguments* args = readArgumentInvocation(); + return SASS_MEMORY_NEW(ItplFnExpression, + scanner.relevantSpanFrom(start), identifier, args, ns); + } + + CallableArguments* args = readArgumentInvocation(false, + StringUtils::equalsIgnoreCase(plain, "var", 3)); + FunctionExpressionObj fn = SASS_MEMORY_NEW(FunctionExpression, + scanner.relevantSpanFrom(start), plain, args, ns); + // sass::string name(identifier->getPlainString()); + return fn.detach(); + } + else { + return SASS_MEMORY_NEW(StringExpression, + identifier->pstate(), identifier); + } + + } + // readIdentifierLike + + // If [name] is the name of a function with special syntax, consumes it. + // Otherwise, returns `null`. [start] is the location before the beginning of [name]. + StringExpression* StylesheetParser::trySpecialFunction(sass::string name, const Offset& start) + { + uint8_t next = 0; + makeLowerCase(name); + InterpolationBuffer buffer(scanner); + sass::string normalized(StringUtils::unvendor(name)); + + if (normalized == "element" || normalized == "expression" + || (normalized == "calc" && normalized != name)) { + if (!scanner.scanChar($lparen)) return nullptr; + buffer.write(name); + buffer.write($lparen); + } + /* + else if (normalized == "min" || normalized == "max") { + // min() and max() are parsed as the plain CSS mathematical functions if + // possible, and otherwise are parsed as normal Sass functions. + StringScannerState beginningOfContents = scanner.state(); + if (!scanner.scanChar($lparen)) return nullptr; + scanWhitespace(); + + buffer.write(name); + buffer.write($lparen); + + if (!tryMinMaxContents(buffer)) { + scanner.backtrack(beginningOfContents); + return nullptr; + } + + SourceSpan pstate(scanner.relevantSpanFrom(start)); + return SASS_MEMORY_NEW(StringExpression, + pstate, buffer.getInterpolation(pstate)); + } + */ + else if (normalized == "progid") { + if (!scanner.scanChar($colon)) return nullptr; + buffer.write(name); + buffer.write($colon); + while (scanner.peekChar(next) && + (isAlphabetic(next) || next == $dot)) { + buffer.write(scanner.readChar()); + } + scanner.expectChar($lparen); + buffer.write($lparen); + } + else if (normalized == "url") { + InterpolationObj contents = tryUrlContents(start); + if (contents == nullptr) return nullptr; + return SASS_MEMORY_NEW(StringExpression, + scanner.relevantSpanFrom(start), contents); + } + /* + else if (normalized == "clamp") { + // Vendor-prefixed clamp() functions aren't parsed specially, because + // no browser has ever supported clamp() with a prefix. + if (name != "clamp") return nullptr; + if (!scanner.scanChar($lparen)) return nullptr; + buffer.write(name); + buffer.write($lparen); + } + */ + else { + return nullptr; + } + + buffer.addInterpolation(readInterpolatedDeclarationValue(true)); + scanner.expectChar($rparen); + buffer.write($rparen); + + SourceSpan pstate(scanner.relevantSpanFrom(start)); + return SASS_MEMORY_NEW(StringExpression, + pstate, buffer.getInterpolation(pstate)); + } + // trySpecialFunction + + /* + // Consumes the contents of a plain-CSS `min()` or `max()` function into + // [buffer] if one is available. Returns whether this succeeded. If [allowComma] + // is `true` (the default), this allows `CalcValue` productions separated by commas. + bool StylesheetParser::tryMinMaxContents(InterpolationBuffer& buffer, bool allowComma) + { + uint8_t next = 0; + // The number of open parentheses that need to be closed. + while (true) { + if (!scanner.peekChar(next)) { + return false; + } + switch (next) { + case $minus: + case $plus: + case $0: + case $1: + case $2: + case $3: + case $4: + case $5: + case $6: + case $7: + case $8: + case $9: + buffer.add(readNumberExpression()); + break; + + case $hash: + if (scanner.peekChar(1) != $lbrace) return false; + buffer.add(readSingleInterpolation()); + break; + + case $c: + case $C: + switch (scanner.peekChar(1)) { + case $a: + case $A: + if (!tryMinMaxFunction(buffer, "calc")) return false; + break; + + case $l: + case $L: + if (!tryMinMaxFunction(buffer, "clamp")) return false; + break; + } + break; + + case $e: + case $E: + if (!tryMinMaxFunction(buffer, "env")) return false; + break; + + case $v: + case $V: + if (!tryMinMaxFunction(buffer, "var")) return false; + break; + + case $lparen: + buffer.write(scanner.readChar()); + if (!tryMinMaxContents(buffer, false)) return false; + break; + + case $m: + case $M: + scanner.readChar(); + if (scanIdentChar($i)) { + if (!scanIdentChar($n)) return false; + buffer.write("min("); + } + else if (scanIdentChar($a)) { + if (!scanIdentChar($x)) return false; + buffer.write("max("); + } + else { + return false; + } + if (!scanner.scanChar($lparen)) return false; + + if (!tryMinMaxContents(buffer)) return false; + break; + + default: + return false; + } + + scanWhitespace(); + + next = scanner.peekChar(); + switch (next) { + case $rparen: + buffer.write(scanner.readChar()); + return true; + + case $plus: + case $minus: + case $asterisk: + case $slash: + buffer.write($space); + buffer.write(scanner.readChar()); + buffer.write($space); + break; + + case $comma: + if (!allowComma) return false; + buffer.write(scanner.readChar()); + buffer.write($space); + break; + + default: + return false; + } + + scanWhitespace(); + } + } + */ + // EO tryMinMaxContents + + // Consumes a function named [name] containing an + // `InterpolatedDeclarationValue` if possible, and + // adds its text to [buffer]. Returns whether such a + // function could be consumed. + /* + bool StylesheetParser::tryMinMaxFunction(InterpolationBuffer& buffer, sass::string name) + { + if (!scanIdentifier(name)) return false; + if (!scanner.scanChar($lparen)) return false; + buffer.write(name); + buffer.write($lparen); + buffer.addInterpolation(readInterpolatedDeclarationValue(true)); + buffer.write($rparen); + if (!scanner.scanChar($rparen)) return false; + return true; + } + */ + // tryMinMaxFunction + + // Like [_urlContents], but returns `null` if the URL fails to parse. + // [start] is the position before the beginning of the name. + // [name] is the function's name; it defaults to `"url"`. + Interpolation* StylesheetParser::tryUrlContents(const Offset& start, sass::string name) + { + // NOTE: this logic is largely duplicated in Parser.tryUrl. + // Most changes here should be mirrored there. + StringScannerState beginningOfContents = scanner.state(); + if (!scanner.scanChar($lparen)) return nullptr; + scanWhitespaceWithoutComments(); + + // Match Ruby Sass's behavior: parse a raw URL() if possible, and if not + // backtrack and re-parse as a function expression. + InterpolationBuffer buffer(scanner); + buffer.write(name.empty() ? "url" : name); + buffer.write($lparen); + while (true) { + uint8_t next = scanner.peekChar(); + if (next == $nul) { + break; + } + else if (next == $backslash) { + escape(buffer.text); + } + else if (next == $hash && scanner.peekChar(1) == $lbrace) { + buffer.add(readSingleInterpolation()); + } + else if (next == $exclamation || + next == $percent || + next == $ampersand || + next == $hash || + (next >= $asterisk && next <= $tilde) || + next >= 0x0080) { + buffer.write(scanner.readChar()); + } + else if (isWhitespace(next)) { + scanWhitespaceWithoutComments(); + if (scanner.peekChar() != $rparen) break; + } + else if (next == $rparen) { + buffer.write(scanner.readChar()); + return buffer.getInterpolation(scanner.relevantSpanFrom(start)); + } + else { + break; + } + } + + scanner.backtrack(beginningOfContents); + return nullptr; + + } + // tryUrlContents + + // Consumes a [url] token that's allowed to contain SassScript. + // Returns either a `StringExpression` or a `FunctionExpression` + Expression* StylesheetParser::readFunctionOrStringExpression() + { + Offset start(scanner.offset); + expectIdentifier("url", "\"url\""); + String* fnName = SASS_MEMORY_NEW(String, + scanner.relevantSpanFrom(start), "url"); + InterpolationObj itpl = SASS_MEMORY_NEW(Interpolation, + scanner.relevantSpanFrom(start), fnName); + InterpolationObj contents = tryUrlContents(start); + if (contents != nullptr) { + return SASS_MEMORY_NEW(StringExpression, + scanner.relevantSpanFrom(start), contents); + } + + SourceSpan pstate(scanner.relevantSpanFrom(start)); + CallableArguments* args = readArgumentInvocation(); + + // Plain Css as it's interpolated + if (itpl->getPlainString().empty()) { + return SASS_MEMORY_NEW(ItplFnExpression, + scanner.relevantSpanFrom(start), itpl, args, ""); + } + + return SASS_MEMORY_NEW(FunctionExpression, + pstate, itpl->getPlainString(), args, ""); + } + // readFunctionOrStringExpression + + // Consumes tokens up to "{", "}", ";", or "!". + // This respects string and comment boundaries and supports interpolation. + // Once this interpolation is evaluated, it's expected to be re-parsed. + // Differences from [parseInterpolatedDeclarationValue] include: + // * This does not balance brackets. + // * This does not interpret backslashes, since + // the text is expected to be re-parsed. + // * This supports Sass-style single-line comments. + // * This does not compress adjacent whitespace characters. + Interpolation* StylesheetParser::readAlmostAnyValue(bool omitComments) + { + // const char* start = scanner.position; + InterpolationBuffer buffer(scanner); + const char* commentStart; + StringExpressionObj strex; + StringScannerState start = scanner.state(); + Interpolation* contents; + uint8_t next = 0; + + while (true) { + if (!scanner.peekChar(next)) { + goto endOfLoop; + } + switch (next) { + case $backslash: + // Write a literal backslash because this text will be re-parsed. + buffer.write(scanner.readChar()); + // ToDo: Check unexpected file end here? + buffer.write(scanner.readChar()); + break; + + case $apos: + case $quote: + strex = readInterpolatedString(); + buffer.addInterpolation(strex->getAsInterpolation()); + break; + + case $slash: + commentStart = scanner.position; + if (scanComment()) { + if (!omitComments) buffer.write(scanner.substring(commentStart)); + } + else { + buffer.write(scanner.readChar()); + } + break; + + case $hash: + if (scanner.peekChar(1) == $lbrace) { + // Add a full interpolated identifier to handle cases like + // "#{...}--1", since "--1" isn't a valid identifier on its own. + auto foo = readInterpolatedIdentifier(); + buffer.addInterpolation(foo); + } + else { + buffer.write(scanner.readChar()); + } + break; + + case $cr: + case $lf: + case $ff: + if (isIndented()) goto endOfLoop; + buffer.write(scanner.readChar()); + break; + + case $exclamation: + case $semicolon: + case $lbrace: + case $rbrace: + goto endOfLoop; + + case $u: + case $U: + start = scanner.state(); + if (!scanIdentifier("url")) { + buffer.write(scanner.readChar()); + break; + } + contents = tryUrlContents(start.offset); + if (contents == nullptr) { + scanner.backtrack(start); + buffer.write(scanner.readChar()); + } + else { + buffer.addInterpolation(contents); + } + break; + + default: + if (lookingAtIdentifier()) { + buffer.write(readIdentifier()); + } + else { + buffer.write(scanner.readChar()); + } + break; + } + } + + endOfLoop: + // scanner.relevant + // scanner.backtrack(scanner.relevant); + // scanWhitespaceWithoutComments(); // consume trailing white-space + return buffer.getInterpolation(scanner.rawSpanFrom(start.offset), false); + + } + // readAlmostAnyValue + + // Consumes tokens until it reaches a top-level `";"`, `")"`, `"]"`, or `"}"` and returns + // their contents as a string. If [allowEmpty] is `false` (the default), this requires + // at least one token. If [allowSemicolon] is `true`, this doesn't stop at semicolons + // and instead includes them in the interpolated output. If [allowColon] is `false`, + // this stops at top-level colons.Unlike [declarationValue], this allows interpolation. + Interpolation* StylesheetParser::readInterpolatedDeclarationValue(bool allowEmpty, bool allowSemicolon, bool allowColon) + { + // NOTE: this logic is largely duplicated in Parser.declarationValue and + // isIdentifier in utils.dart. Most changes here should be mirrored there. + StringScannerState beforeUrl = scanner.state(); + Interpolation* contents; + + InterpolationBuffer buffer(scanner); + Offset start(scanner.offset); + sass::vector brackets; + bool wroteNewline = false; + uint8_t next = 0; + + InterpolationObj itpl; + StringExpressionObj strex; + + while (true) { + if (!scanner.peekChar(next)) { + goto endOfLoop; + } + switch (next) { + case $backslash: + escape(buffer.text, true); + wroteNewline = false; + break; + + case $apos: + case $quote: + strex = readInterpolatedString(); + itpl = strex->getAsInterpolation(); + buffer.addInterpolation(itpl); + wroteNewline = false; + break; + + case $slash: + if (scanner.peekChar(1) == $asterisk) { + buffer.write(rawText(&StylesheetParser::scanLoudComment)); + } + else { + buffer.write(scanner.readChar()); + } + wroteNewline = false; + break; + + case $hash: + if (scanner.peekChar(1) == $lbrace) { + // Add a full interpolated identifier to handle cases like + // "#{...}--1", since "--1" isn't a valid identifier on its own. + itpl = readInterpolatedIdentifier(); + buffer.addInterpolation(itpl); + } + else { + buffer.write(scanner.readChar()); + } + wroteNewline = false; + break; + + case $space: + case $tab: + if (wroteNewline || !isWhitespace(scanner.peekChar(1))) { + buffer.write(scanner.readChar()); + } + else { + scanner.readChar(); + } + break; + + case $lf: + case $cr: + case $ff: + if (isIndented()) goto endOfLoop; + if (!isNewline(scanner.peekChar(-1))) { + buffer.write("\n"); + } + scanner.readChar(); + wroteNewline = true; + break; + + case $lparen: + case $lbrace: + case $lbracket: + buffer.write(next); + brackets.emplace_back(opposite(scanner.readChar())); + wroteNewline = false; + break; + + case $rparen: + case $rbrace: + case $rbracket: + if (brackets.empty()) goto endOfLoop; + buffer.write(next); + scanner.expectChar(brackets.back()); + brackets.pop_back(); + wroteNewline = false; + break; + + case $semicolon: + if (!allowSemicolon && brackets.empty()) goto endOfLoop; + buffer.write(scanner.readChar()); + wroteNewline = false; + break; + + case $colon: + if (!allowColon && brackets.empty()) goto endOfLoop; + buffer.writeCharCode(scanner.readChar()); + wroteNewline = false; + break; + + case $u: + case $U: + beforeUrl = scanner.state(); + if (!scanIdentifier("url")) { + buffer.write(scanner.readChar()); + wroteNewline = false; + break; + } + + contents = tryUrlContents(beforeUrl.offset); + if (contents == nullptr) { + scanner.backtrack(beforeUrl); + buffer.write(scanner.readChar()); + } + else { + buffer.addInterpolation(contents); + } + wroteNewline = false; + break; + + default: + if (next == $nul) goto endOfLoop; + + if (lookingAtIdentifier()) { + buffer.write(readIdentifier()); + } + else { + buffer.write(scanner.readChar()); + } + wroteNewline = false; + break; + } + } + + endOfLoop: + + if (!brackets.empty()) scanner.expectChar(brackets.back()); + if (!allowEmpty && buffer.empty()) { + error("Expected token.", + scanner.relevantSpan()); + } + SourceSpan pstate(scanner.rawSpanFrom(start)); + return buffer.getInterpolation(pstate); + + } + // parseInterpolatedDeclarationValue + + // Consumes an identifier that may contain interpolation. + Interpolation* StylesheetParser::readInterpolatedIdentifier() + { + InterpolationBuffer buffer(scanner); + Offset start(scanner.offset); + + if (scanner.scanChar($minus)) { + buffer.writeCharCode($minus); + if (scanner.scanChar($minus)) { + buffer.writeCharCode($minus); + consumeInterpolatedIdentifierBody(buffer); + return buffer.getInterpolation(scanner.relevantSpanFrom(start)); + } + } + + uint8_t first = 0; // , next = 0; + if (!scanner.peekChar(first)) { + error("Expected identifier.", + scanner.relevantSpanFrom(start)); + } + else if (isNameStart(first)) { + buffer.write(scanner.readChar()); + } + else if (first == $backslash) { + escape(buffer.text, true); + } + else if (first == $hash && scanner.peekChar(1) == $lbrace) { + ExpressionObj ex = readSingleInterpolation(); + buffer.add(ex.ptr()); + } + else { + error("Expected identifier.", + scanner.relevantSpan()); + } + + consumeInterpolatedIdentifierBody(buffer); + return buffer.getInterpolation(scanner.relevantSpanFrom(start)); + + } + + void StylesheetParser::consumeInterpolatedIdentifierBody(InterpolationBuffer& buffer) + { + + uint8_t /* first = 0, */ next = 0; + while (true) { + if (!scanner.peekChar(next)) { + break; + } + else if (next == $underscore || + next == $minus || + isAlphanumeric(next) || + next >= 0x0080) { + buffer.write(scanner.readChar()); + } + else if (next == $backslash) { + escape(buffer.text); + } + else if (next == $hash && scanner.peekChar(1) == $lbrace) { + buffer.add(readSingleInterpolation()); + } + else { + break; + } + } + + } + // readInterpolatedIdentifier + + // Consumes interpolation. + Expression* StylesheetParser::readSingleInterpolation() + { + Offset start(scanner.offset); + scanner.expect("#{"); + scanWhitespace(); + ExpressionObj contents(readExpression()); + scanner.expectChar($rbrace); + + if (plainCss()) { + error( + "Interpolation isn't allowed in plain CSS.", + scanner.rawSpanFrom(start)); + } + + return contents.detach(); + } + // readSingleInterpolation + + // Consumes a list of media queries. + Interpolation* StylesheetParser::readMediaQueryList() + { + //std::cerr << "read media query list\n"; + Offset start(scanner.offset); + InterpolationBuffer buffer(scanner); + while (true) { + scanWhitespace(); + readMediaQuery(buffer); + scanWhitespace(); + if (!scanner.scanChar($comma)) break; + buffer.write($comma); + buffer.write($space); + } + return buffer.getInterpolation(scanner.relevantSpanFrom(start)); + } + // readMediaQueryList + + + // Consumes one or more `MediaOrInterp` expressions + // separated by [operator] and writes them to [buffer]. + void StylesheetParser::readMediaLogicSequence( + InterpolationBuffer& buffer, sass::string op) + { + //std::cerr << "read media logic sequence\n"; + while (true) { + readMediaOrInterp(buffer); + scanWhitespace(); + + if (!scanIdentifier(op)) return; + expectWhitespace(); + + buffer.writeCharCode($space); + buffer.write(op); + buffer.writeCharCode($space); + } + } + + + // Consumes a `MediaOrInterp` expression and writes it to [buffer]. + void StylesheetParser::readMediaOrInterp(InterpolationBuffer& buffer) + { + if (scanner.peekChar() == $hash) { + Expression* interpolation = readSingleInterpolation(); + Interpolation* itpl = SASS_MEMORY_NEW(Interpolation, interpolation->pstate()); + itpl->append(interpolation); + buffer.addInterpolation(itpl); + } + else { + readMediaInParens(buffer); + } + } + + // Consumes a `MediaInParens` expression and writes it to [buffer]. + void StylesheetParser::readMediaInParens(InterpolationBuffer& buffer) + { + // std::cerr << "read media in parens\n"; + scanner.expectChar($lparen, "media condition in parentheses"); + buffer.writeCharCode($lparen); + scanWhitespace(); + + if (scanner.peekChar() == $lparen) { + readMediaInParens(buffer); + scanWhitespace(); + if (scanIdentifier("and")) { + buffer.write(" and "); + expectWhitespace(); + readMediaLogicSequence(buffer, "and"); + } + else if (scanIdentifier("or")) { + buffer.write(" or "); + expectWhitespace(); + readMediaLogicSequence(buffer, "or"); + } + } + else if (scanIdentifier("not")) { + buffer.write("not "); + expectWhitespace(); + readMediaOrInterp(buffer); + } + else { + buffer.add(readExpressionUntilComparison()); + if (scanner.scanChar($colon)) { + scanWhitespace(); + buffer.writeCharCode($colon); + buffer.writeCharCode($space); + buffer.add(readExpression()); + } + else { + auto next = scanner.peekChar(); + if (next == $lt || next == $gt || next == $equal) { + buffer.writeCharCode($space); + buffer.writeCharCode(scanner.readChar()); + if (next == $lt || next == $gt) { + if (scanner.scanChar($equal)) { + buffer.writeCharCode($equal); + } + } + buffer.writeCharCode($space); + + scanWhitespace(); + buffer.add(readExpressionUntilComparison()); + + // dart-lang/sdk#45356 + if (next == $lt || next == $gt) { + if (scanner.scanChar(next)) { + buffer.writeCharCode($space); + buffer.writeCharCode(next); + if (scanner.scanChar($equal)) + buffer.writeCharCode($equal); + buffer.writeCharCode($space); + + scanWhitespace(); + buffer.add(readExpressionUntilComparison()); + } + } + } + } + } + // std::cerr << "buffer " << buffer.text << "\n"; + scanner.expectChar($rparen); + scanWhitespace(); + buffer.writeCharCode($rparen); + } + + + // Consumes a single media query and appends it to [buffer]. + void StylesheetParser::readMediaQuery(InterpolationBuffer& buffer) + { + //std::cerr << "read media query\n"; + + if (scanner.peekChar() == $lparen) { + readMediaInParens(buffer); + scanWhitespace(); + if (scanIdentifier("and")) { + buffer.write(" and "); + expectWhitespace(); + readMediaLogicSequence(buffer, "and"); + } + else if (scanIdentifier("or")) { + buffer.write(" or "); + expectWhitespace(); + readMediaLogicSequence(buffer, "or"); + } + return; + } + + auto identifier1 = readInterpolatedIdentifier(); + if (equalsIgnoreCase(identifier1->getPlainString(), "not")) { + // For example, "@media not (...) {" + expectWhitespace(); + + if (!lookingAtInterpolatedIdentifier()) { + buffer.write("not "); + readMediaOrInterp(buffer); + return; + } + } + + scanWhitespace(); + buffer.addInterpolation(identifier1); + if (!lookingAtInterpolatedIdentifier()) { + // For example, "@media screen {". + return; + } + + buffer.writeCharCode($space); + auto identifier2 = readInterpolatedIdentifier(); + + if (equalsIgnoreCase(identifier2->getPlainString(), "and")) { + expectWhitespace(); + // For example, "@media screen and ..." + buffer.write(" and "); + } + else { + scanWhitespace(); + buffer.addInterpolation(identifier2); + if (scanIdentifier("and")) { + // For example, "@media only screen and ..." + expectWhitespace(); + buffer.write(" and "); + } + else { + // For example, "@media only screen {" + return; + } + } + + // We've consumed either `IDENTIFIER "and"` or + // `IDENTIFIER IDENTIFIER "and"`. + + if (scanIdentifier("not")) { + // For example, "@media screen and not (...) {" + expectWhitespace(); + buffer.write("not "); + readMediaOrInterp(buffer); + return; + } + + readMediaLogicSequence(buffer, "and"); + } + // readMediaQuery + + // Consumes a media query feature. + Interpolation* StylesheetParser::readMediaFeature() + { + if (scanner.peekChar() == $hash) { + Expression* interpolation = readSingleInterpolation(); + Interpolation* itpl = SASS_MEMORY_NEW(Interpolation, + interpolation->pstate()); + itpl->append(interpolation); + return itpl; + } + + Offset start(scanner.offset); + InterpolationBuffer buffer(scanner); + scanner.expectChar($lparen); + buffer.write($lparen); + scanWhitespace(); + + buffer.add(readExpressionUntilComparison()); + if (scanner.scanChar($colon)) { + scanWhitespace(); + buffer.write($colon); + buffer.write($space); + buffer.add(readExpression()); + } + else { + uint8_t next = scanner.peekChar(); + bool isAngle = next == $lt || next == $gt; + if (isAngle || next == $equal) { + buffer.write($space); + buffer.write(scanner.readChar()); + if (isAngle && scanner.scanChar($equal)) { + buffer.write($equal); + } + buffer.write($space); + + scanWhitespace(); + buffer.add(readExpressionUntilComparison()); + + if (isAngle && scanner.scanChar(next)) { + buffer.write($space); + buffer.write(next); + if (scanner.scanChar($equal)) { + buffer.write($equal); + } + buffer.write($space); + + scanWhitespace(); + buffer.add(readExpressionUntilComparison()); + } + } + } + + scanner.expectChar($rparen); + scanWhitespace(); + buffer.write($rparen); + + return buffer.getInterpolation(scanner.relevantSpanFrom(start)); + + } + // readMediaFeature + + // Helper function for until condition + bool StylesheetParser::lookingAtExpressionEnd() + { + uint8_t next = scanner.peekChar(); + if (next == $equal) return scanner.peekChar(1) != $equal; + return next == $lt || next == $gt; + } + // lookingAtExpressionEnd + + // Consumes an expression until it reaches a + // top-level `<`, `>`, or a `=` that's not `==`. + Expression* StylesheetParser::readExpressionUntilComparison() + { + return readExpression(false, false, + &StylesheetParser::lookingAtExpressionEnd); + } + + // Consumes a `@supports` condition. + SupportsCondition* StylesheetParser::readSupportsCondition() + { + Offset start(scanner.offset); + + if (scanIdentifier("not")) { + scanWhitespace(); + return SASS_MEMORY_NEW(SupportsNegation, + scanner.relevantSpanFrom(start), readSupportsConditionInParens()); + } + + SupportsConditionObj condition = + readSupportsConditionInParens(); + scanWhitespace(); + bool hasOp = false; + SupportsOperation::Operand op; + while (lookingAtIdentifier()) { + if (hasOp) { + if (op == SupportsOperation::AND) + expectIdentifier("and", "\"and\""); + else + expectIdentifier("or", "\"or\""); + } + else if (scanIdentifier("or")) { + op = SupportsOperation::OR; + hasOp = true; + } + else { + expectIdentifier("and", "\"and\""); + op = SupportsOperation::AND; + hasOp = true; + } + scanWhitespace(); + SupportsConditionObj right = + readSupportsConditionInParens(); + condition = SASS_MEMORY_NEW(SupportsOperation, + scanner.relevantSpanFrom(start), condition, right, op); + scanWhitespace(); + } + return condition.detach(); + } + // EO readSupportsCondition + + // Consumes a parenthesized supports condition, or an interpolation. + SupportsCondition* StylesheetParser::readSupportsConditionInParens() + { + Offset start(scanner.offset); + + if (lookingAtInterpolatedIdentifier()) { + InterpolationObj identifier = readInterpolatedIdentifier(); + sass::string initialPlain(identifier->getInitialPlain()); + if (StringUtils::equalsIgnoreCase(initialPlain, "not", 3)) { + error("\"not\" is not a valid identifier here.", identifier->pstate()); + } + + if (scanner.scanChar($lparen)) { + InterpolationObj arguments = + readInterpolatedDeclarationValue(true, true); + scanner.expectChar($rparen); + return SASS_MEMORY_NEW(SupportsFunction, + scanner.relevantSpanFrom(start), + identifier, arguments); + } + else if (identifier->size() != 1 || !identifier->first()->isaExpression()) { + error("Expected @supports condition.", identifier->pstate()); + } + return SASS_MEMORY_NEW(SupportsInterpolation, + scanner.relevantSpanFrom(start), + identifier->first()->isaExpression()); + } + + scanner.expectChar($lparen); + scanWhitespace(); + if (scanIdentifier("not")) { + scanWhitespace(); + SupportsConditionObj condition + = readSupportsConditionInParens(); + scanner.expectChar($rparen); + return SASS_MEMORY_NEW(SupportsNegation, + scanner.relevantSpanFrom(start), + condition); + } + else if (scanner.peekChar() == $lparen) { + SupportsConditionObj condition + = readSupportsCondition(); + scanner.expectChar($rparen); + return condition.detach(); + } + + ExpressionObj name; + StringScannerState state(scanner.state()); + try { + name = readExpression(); + scanner.expectChar($colon); + } + catch (Exception::ParserException& err) { + + scanner.backtrack(state); + InterpolationObj identifier = readInterpolatedIdentifier(); + SupportsOperationObj operation = trySupportsOperation(identifier, start); + if (operation != nullptr) { + scanner.expectChar($rparen); + return operation.detach(); + } + + // If parsing an expression fails, try to parse an + // `InterpolatedAnyValue` instead. But if that value runs into a + // top-level colon, then this is probably intended to be a declaration + // after all, so we rethrow the declaration-parsing error. + InterpolationBuffer buffer(scanner); + buffer.addInterpolation(identifier); + buffer.addInterpolation(readInterpolatedDeclarationValue(true, true, false)); + if (scanner.peekChar() == $colon) throw err; + scanner.expectChar($rparen); + + return SASS_MEMORY_NEW(SupportsAnything, scanner.relevantSpanFrom(start), + buffer.getInterpolation(scanner.relevantSpanFrom(start), false)); + } + + SupportsDeclarationObj declaration = readSupportsDeclarationValue(name, start); + scanner.expectChar($rparen); + return declaration.detach(); + + //scanWhitespace(); + //ExpressionObj value(readExpression()); + //scanner.expectChar($rparen); + // + //return SASS_MEMORY_NEW(SupportsDeclaration, + // scanner.relevantSpanFrom(start), name, value); + } + // EO readSupportsConditionInParens + + // Tries to consume a negated supports condition. Returns `null` if it fails. + SupportsNegation* StylesheetParser::trySupportsNegation() + { + StringScannerState start = scanner.state(); + if (!scanIdentifier("not") || scanner.isDone()) { + scanner.backtrack(start); + return nullptr; + } + + uint8_t next = scanner.peekChar(); + if (!isWhitespace(next) && next != $lparen) { + scanner.backtrack(start); + return nullptr; + } + + scanWhitespace(); + + return SASS_MEMORY_NEW(SupportsNegation, + scanner.relevantSpanFrom(start.offset), + readSupportsConditionInParens()); + + } + // trySupportsNegation + + // Parses and returns the right-hand side of + // a declaration in a supports query. + SupportsDeclaration* StylesheetParser::readSupportsDeclarationValue( + Expression* name, Offset& start) + { + ExpressionObj value; + if (StringExpression* ex = name->isaStringExpression()) { + if (ex->text() != nullptr && ex->hasQuotes() == false) { + auto plain = ex->text()->getInitialPlain(); + if (strncmp(plain.c_str(), "--", 2) == 0) { + value = SASS_MEMORY_NEW(StringExpression, + scanner.rawSpanFrom(start), + readInterpolatedDeclarationValue()); + return SASS_MEMORY_NEW(SupportsDeclaration, + scanner.relevantSpanFrom(start), + name, value); + } + } + } + scanWhitespace(); + value = readExpression(); + return SASS_MEMORY_NEW(SupportsDeclaration, + scanner.relevantSpanFrom(start), + name, value); + } + // EO readSupportsDeclarationValue + + // If [interpolation] is followed by `"and"` or `"or"`, parse it as a supports + // operation. Otherwise, return `null` without moving the scanner position. + SupportsOperation* StylesheetParser::trySupportsOperation( + Interpolation* interpolation, Offset& start) + { + if (interpolation->size() != 1) return nullptr; + Interpolant* expression = interpolation->first(); + if (!expression->isaExpression()) return nullptr; + StringScannerState state(scanner.state()); + + scanWhitespace(); + + bool hasOp = false; + SupportsOperation::Operand op; + SupportsOperationObj operation{}; + while (lookingAtIdentifier()) { + if (hasOp) { + if (op == SupportsOperation::AND) + expectIdentifier("and", "\"and\""); + else + expectIdentifier("or", "\"or\""); + } + else if (scanIdentifier("or")) { + op = SupportsOperation::OR; + hasOp = true; + } + else if (scanIdentifier("and")) { + op = SupportsOperation::AND; + hasOp = true; + } + else { + scanner.backtrack(state); + return nullptr; + } + + scanWhitespace(); + + SupportsConditionObj rhs = readSupportsConditionInParens(); + + if (operation != nullptr) { + operation = SASS_MEMORY_NEW(SupportsOperation, + scanner.rawSpanFrom(start), + operation.ptr(), rhs, op); + } + else { + SupportsInterpolationObj wrapped = SASS_MEMORY_NEW( + SupportsInterpolation, interpolation->pstate(), + expression->isaExpression()); + operation = SASS_MEMORY_NEW(SupportsOperation, + scanner.rawSpanFrom(start), + wrapped.ptr(), rhs, op); + } + + scanWhitespace(); + } + + return operation.detach(); + } + + // Returns whether the scanner is immediately before an identifier that may contain + // interpolation. This is based on the CSS algorithm, but it assumes all backslashes + // start escapes and it considers interpolation to be valid in an identifier. + // https://drafts.csswg.org/css-syntax-3/#would-start-an-identifier + bool StylesheetParser::lookingAtInterpolatedIdentifier() const + { + // See also [ScssParser._lookingAtIdentifier]. + + uint8_t first = scanner.peekChar(); + if (first == $nul) return false; + if (isNameStart(first) || first == $backslash) return true; + if (first == $hash) return scanner.peekChar(1) == $lbrace; + + if (first != $minus) return false; + uint8_t second = scanner.peekChar(1); + if (second == $nul) return false; + // if (isNameStart(second)) return true; + // if (second == $backslash) return true; + + if (second == $hash) return scanner.peekChar(2) == $lbrace; + // if (second != $minus) return false; + + // uint8_t third = scanner.peekChar(2); + // if (third == $nul) return false; + // if (third != $hash) return isNameStart(third); + // else return scanner.peekChar(3) == $lbrace; + + return isNameStart(second) + || second == $backslash + || second == $minus; + } + // EO lookingAtInterpolatedIdentifier + + // Returns whether the scanner is immediately before a sequence + // of characters that could be part of an CSS identifier body. + // The identifier body may include interpolation. + bool StylesheetParser::lookingAtInterpolatedIdentifierBody() const + { + uint8_t first = scanner.peekChar(); + if (first == $nul) return false; + if (isName(first) || first == $backslash) return true; + return first == $hash && scanner.peekChar(1) == $lbrace; + } + // EO lookingAtInterpolatedIdentifierBody + + // Returns whether the scanner is immediately before a SassScript expression. + bool StylesheetParser::lookingAtExpression() const + { + uint8_t character, next; + if (!scanner.peekChar(character)) { + return false; + } + if (character == $dot) { + return scanner.peekChar(1) != $dot; + } + if (character == $exclamation) { + if (!scanner.peekChar(next, 1)) { + } + return isWhitespace(next) + || equalsLetterIgnoreCase($i, next); + } + + return character == $lparen + || character == $slash + || character == $lbracket + || character == $apos + || character == $quote + || character == $hash + || character == $plus + || character == $minus + || character == $backslash + || character == $dollar + || character == $ampersand + || isNameStart(character) + || isDigit(character); + } + // EO lookingAtExpression + + // Like [identifier], but rejects identifiers that begin with `_` or `-`. + sass::string StylesheetParser::readPublicIdentifier() + { + Offset start(scanner.offset); + auto result = readIdentifier(); + + uint8_t first = result[0]; + if (first == $minus || first == $underscore) { + error("Private members can't be accessed from outside their modules.", + scanner.rawSpanFrom(start)); + } + + return result; + } + // EO readPublicIdentifier + +} diff --git a/src/parser_stylesheet.hpp b/src/parser_stylesheet.hpp new file mode 100644 index 0000000000..31c410b148 --- /dev/null +++ b/src/parser_stylesheet.hpp @@ -0,0 +1,575 @@ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#ifndef SASS_PARSER_STYLESHEET_HPP +#define SASS_PARSER_STYLESHEET_HPP + +// sass.hpp must go before all system headers +// to get the __EXTENSIONS__ fix on Solaris. +#include "capi_sass.hpp" + +#include +#include "parser_base.hpp" +#include "ast_statements.hpp" + +namespace Sass { + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + class StylesheetParser : public BaseParser + { + + friend class ExpressionParser; + + protected: + + // Current parsing recursion depth + // Just a counter for nesting guard + size_t recursion = 0; + + public: + + // Whether we've consumed a rule other than `@charset`, `@forward`, or `@use`. + bool isUseAllowed = true; + + // Whether the parser is currently parsing the contents of a mixin declaration. + bool inMixin = false; + + // Whether the current mixin contains at least one `@content` rule. + bool mixinHasContent = false; // Removed in dart? + + // Whether the parser is currently parsing a content block passed to a mixin. + bool inContentBlock = false; + + // Whether the parser is currently parsing a control directive such as `@if` or `@each`. + bool inControlDirective = false; + + // Whether the parser is currently parsing an unknown rule. + bool inUnknownAtRule = false; + + // Whether the parser is currently parsing a style rule. + bool inStyleRule = false; + + // Whether the parser is currently within a parenthesized expression. + bool inParentheses = false; + + // Whether the parser is currently parsing root rules + bool inRoot = true; + + bool _exclusiveAtForRule; + bool _foundForRuleExpression; + + public: + + // Value constructor + StylesheetParser( + Compiler& context, + SourceDataObj source) : + BaseParser( + context, source), + recursion(0), + isUseAllowed(true), + inMixin(false), + mixinHasContent(false), + inContentBlock(false), + inControlDirective(false), + inUnknownAtRule(false), + inStyleRule(false), + inParentheses(false), + inRoot(true), + _exclusiveAtForRule(false), + _foundForRuleExpression(false) + {} + + // Main parser entry function + Root* parseRoot(); + + // Parse external callback function + ExternalCallable* parseExternalCallable(); + + // Argument declaration is tricky in terms of scoping. + // The variable before the colon is defined on the new frame. + // But the right side is evaluated in the parent scope. + CallableSignature* parseArgumentDeclaration(); + + // Whether this is a plain CSS stylesheet. + virtual bool plainCss() const { return false; } + + // Whether this is parsing the indented syntax. + virtual bool isIndented() const { return false; }; + + protected: + + // Parses and returns a selector used in a style rule. + virtual Interpolation* styleRuleSelector() = 0; + + // Asserts that the scanner is positioned before a statement separator, + // or at the end of a list of statements. If the [name] of the parent + // rule is passed, it's used for error reporting. This consumes + // whitespace, but nothing else, including comments. + virtual void expectStatementSeparator(sass::string name = "") = 0; + + // Whether the scanner is positioned at the end of a statement. + virtual bool atEndOfStatement() = 0; + + // Whether the scanner is positioned before a block of + // children that can be parsed with [children]. + virtual bool lookingAtChildren() = 0; + + // Tries to scan an `@else` rule after an `@if` block. Returns whether + // that succeeded. This should just scan the rule name, not anything + // afterwards. [ifIndentation] is the result of [currentIndentation] + // from before the corresponding `@if` was parsed. + virtual bool scanElse(size_t ifIndentation) = 0; + + // Consumes a block of child statements. Unlike most production consumers, + // this does *not* consume trailing whitespace. This is necessary to ensure + // that the source span for the parent rule doesn't cover whitespace after the rule. + virtual StatementVector readChildren(Statement* (StylesheetParser::* child)()) = 0; + + // Consumes top-level statements. The [statement] callback may return `nullptr`, + // indicating that a statement was consumed that shouldn't be added to the AST. + virtual StatementVector readStatements(Statement* (StylesheetParser::* statement)()) = 0; + + // Consumes a statement that's allowed at the top level of the stylesheet or + // within nested style and at rules. If [root] is `true`, this parses at-rules + // that are allowed only at the root of the stylesheet. + Statement* readStatement(bool root = false); + + // Helpers to either parse root or children context + Statement* readRootStatement() { return readStatement(true); } + Statement* readChildStatement() { return readStatement(false); } + + virtual Expression* readNamespacedExpression(const sass::string& ns, Offset state); + + // Tries to parse a namespaced [VariableDeclaration], and returns the value + // parsed so far if it fails. + // + // This can return either an [Interpolation], indicating that it couldn't + // consume a variable declaration and that property declaration or selector + // parsing should be attempted; or it can return a [VariableDeclaration], + // indicating that it successfully consumed a variable declaration. + bool tryVariableDeclarationOrInterpolation( + AssignRule*& assignment, + Interpolation*& interpolation); + + AssignRule* readVariableDeclarationWithNamespace(); + + void assertPublicIdentifier( + const sass::string& identifier, + Offset start); + + + // Consumes a variable declaration. This never *consumes* a namespace, + // but if [namespace] is passed it will be used for the declaration. + AssignRule* readVariableDeclarationWithoutNamespace( + const sass::string& ns, Offset start); + + // Consumes a style rule. + StyleRule* readStyleRule(Interpolation* itpl = nullptr); + + // Consumes a [Declaration] or a [StyleRule]. + // + // When parsing the contents of a style rule, it can be difficult to tell + // declarations apart from nested style rules. Since we don't thoroughly + // parse selectors until after resolving interpolation, we can share a bunch + // of the parsing of the two, but we need to disambiguate them first. We use + // the following criteria: + // + // * If the entity doesn't start with an identifier followed by a colon, + // it's a selector. There are some additional mostly-unimportant cases + // here to support various declaration hacks. + // + // * If the colon is followed by another colon, it's a selector. + // + // * Otherwise, if the colon is followed by anything other than + // interpolation or a character that's valid as the beginning of an + // identifier, it's a declaration. + // + // * If the colon is followed by interpolation or a valid identifier, try + // parsing it as a declaration value. If this fails, backtrack and parse + // it as a selector. + // + // * If the declaration value is valid but is followed by "{", backtrack and + // parse it as a selector anyway. This ensures that ".foo:bar {" is always + // parsed as a selector and never as a property with nested properties + // beneath it. + Statement* readDeclarationOrStyleRule(); + + Statement* readVariableDeclarationOrStyleRule(); + + // Tries to parse a declaration, and returns the value parsed so + // far if it fails. This can return either an [InterpolationBuffer], + // indicating that it couldn't consume a declaration and that selector + // parsing should be attempted; or it can return a [Declaration], + // indicating that it successfully consumed a declaration. + Statement* tryDeclarationOrBuffer(InterpolationBuffer& buffer); + + // Consumes a property declaration. This is only used in contexts where + // declarations are allowed but style rules are not, such as nested + // declarations. Otherwise, [readDeclarationOrStyleRule] is used instead. + Statement* readPropertyOrVariableDeclaration(bool parseCustomProperties = true); + + // Consumes a statement that's allowed within a declaration. + Statement* readDeclarationOrAtRule(); + + // Consumes an at-rule. This consumes at-rules that are allowed at all levels + // of the document; the [child] parameter is called to consume any at-rules + // that are specifically allowed in the caller's context. If [root] is `true`, + // this parses at-rules that are allowed only at the root of the stylesheet. + virtual Statement* readAtRule(Statement* (StylesheetParser::* child)(), bool root = false); + + // Consumes an at-rule allowed within a property declaration. + Statement* readDeclarationAtRule(); + + // Consumes a statement allowed within a function. + Statement* readFunctionRuleChild(); + + // Consumes an at-rule's name, with interpolation disallowed. + sass::string readPlainAtRuleName(); + + // Consumes an `@at-root` rule. + // [start] should point before the `@`. + AtRootRule* readAtRootRule(Offset start); + + // Consumes a query expression of the form `(foo: bar)`. + Interpolation* readAtRootQuery(); + + // Consumes a `@content` rule. + // [start] should point before the `@`. + ContentRule* readContentRule(Offset start); + + // Consumes a `@debug` rule. + // [start] should point before the `@`. + DebugRule* readDebugRule(Offset start); + + // Consumes an `@each` rule. + // [start] should point before the `@`. [child] is called to consume any + // children that are specifically allowed in the caller's context. + EachRule* readEachRule(Offset start, Statement* (StylesheetParser::* child)()); + + // Consumes a `@error` rule. + // [start] should point before the `@`. + ErrorRule* readErrorRule(Offset start); + + // Consumes a `@extend` rule. + // [start] should point before the `@`. + ExtendRule* readExtendRule(Offset start); + + // Consumes a `@function` rule. + // [start] should point before the `@`. + FunctionRule* readFunctionRule(Offset start); + + // Try to parse either `to` or `through`, if successful + // we will return `true`. The boolean passed via [inclusive] + // will be set to `true` if we parsed `through`. We return + // `false` if neither of the tokens could be parsed. + bool tryForRuleOperator(bool& inclusive); + + // Consumes a `@for` rule. + // [start] should point before the `@`. [child] is called to consume any + // children that are specifically allowed in the caller's context. + ForRule* readForRule(Offset start, Statement* (StylesheetParser::* child)()); + + // Consumes an `@if` rule. + // [start] should point before the `@`. [child] is called to consume any + // children that are specifically allowed in the caller's context. + IfRule* readIfRule(Offset start, Statement* (StylesheetParser::* child)()); + + // Consumes an `@import` rule. + // [start] should point before the `@`. + ImportRule* readImportRule(Offset start); + + // Consumes an argument to an `@import` rule. + // If anything is found it will be added to [rule]. + virtual void scanImportArgument(ImportRule* rule); + + // Returns whether [url] indicates that an `@import` is a plain CSS import. + bool isPlainImportUrl(const sass::string& url) const; + + // Consumes a supports condition and/or a media query after an `@import`. + std::pair tryImportQueries(); + + // Consumes a sequence of modifiers (such as media or supports queries) + // after an import argument. Returns `null` if there are no modifiers. + Interpolation* tryImportModifiers(); + + // Consumes the contents of a `supports()` function after + // an `@import` rule (but not the function name or parentheses). + SupportsCondition* readImportSupportsQuery(); + + // Consumes a function call within a `supports()` + // function after an `@import` if available. + SupportsFunction* tryImportSupportsFunction(); + + // Consumes an `@include` rule. + // [start] should point before the `@`. + IncludeRule* readIncludeRule(Offset start); + + // Consumes a `@media` rule. + // [start] should point before the `@`. + MediaRule* readMediaRule(Offset start); + + // Consumes a mixin declaration. + // [start] should point before the `@`. + MixinRule* readMixinRule(Offset start); + + // Consumes a `@moz-document` rule. Gecko's `@-moz-document` diverges + // from [the specification][] allows the `url-prefix` and `domain` + // functions to omit quotation marks, contrary to the standard. + // [the specification]: http://www.w3.org/TR/css3-conditional/ + AtRule* readMozDocumentRule(Offset start, Interpolation* name); + + // Consumes a `@return` rule. + // [start] should point before the `@`. + ReturnRule* readReturnRule(Offset start); + + // Consumes a `@supports` rule. + // [start] should point before the `@`. + SupportsRule* readSupportsRule(Offset start); + + // Consumes a `@use` rule. + // [start] should point before the `@`. + UseRule* readUseRule(Offset start); + + bool readWithConfiguration( + sass::vector& vars, + bool allowGuarded = false); + + void readForwardMembers( + std::set& variables, + std::set& callables + ); + + // Consumes a `@forward` rule. + // [start] should point before the `@`. + ForwardRule* readForwardRule(Offset start); + + sass::string readUseNamespace(const sass::string& url, const Offset& start); + + // Consumes a `@warn` rule. + // [start] should point before the `@`. + WarnRule* readWarnRule(Offset start); + + // Consumes a `@while` rule. [start] should point before the `@`. [child] is called + // to consume any children that are specifically allowed in the caller's context. + WhileRule* readWhileRule(Offset start, Statement* (StylesheetParser::* child)()); + + // Consumes an at-rule that's not explicitly supported by Sass. + // [start] should point before the `@`. [name] is the name of the at-rule. + AtRule* readAnyAtRule(Offset start, Interpolation* name); + + // Parse almost any value to report disallowed at-rule + Statement* throwDisallowedAtRule(Offset start); + + // Consumes an argument invocation. If [mixin] is `true`, this is parsed + // as a mixin invocation. Mixin invocations don't allow the Microsoft-style + // `=` operator at the top level, but function invocations do. + CallableArguments* readArgumentInvocation(bool mixin = false, bool allowEmptySecondArg = false); + + // Consumes an expression. If [bracketList] is true, parses this expression as + // the contents of a bracketed list. If [singleEquals] is true, allows the + // Microsoft-style `=` operator at the top level. If [until] is passed, it's + // called each time the expression could end and still be a valid expression. + // When it returns `true`, this returns the expression. + Expression* readExpression( + bool bracketList = false, bool singleEquals = false, + bool(StylesheetParser::* until)() = nullptr); + + // Returns `true` if scanner reached a `,` + bool lookingAtComma(); + + bool lookingAtForRuleContinuation(); + + // Consumes an expression until it reaches a top-level comma. If [singleEquals] + // is true, this will allow the Microsoft-style `=` operator at the top level. + Expression* readExpressionUntilComma(bool singleEquals = false); + + // Consumes an expression that doesn't contain any top-level whitespace. + Expression* readSingleExpression(); + + // Consumes a parenthesized expression. + virtual Expression* readParenthesizedExpression(); + + // Not yet evaluated, therefore not Map yet + Expression* readMapExpression(Expression* first, Offset start); + + // Consumes an expression that starts with a `#`. + Expression* readHashExpression(); + + // Consumes the contents of a hex color, after the `#`. + ColorExpression* readColorExpression(StringScannerState start); + + // Returns whether [interpolation] is a plain + // string that can be parsed as a hex color. + bool isHexColor(Interpolation* interpolation) const; + + // Consumes a single hexadecimal digit. + uint8_t readHexDigit(); + + // Consumes an expression that starts with a `+`. + Expression* readPlusExpression(); + + // Consumes an expression that starts with a `-`. + Expression* readMinusExpression(); + + // Consumes an `!important` expression. + StringExpression* readImportantExpression(); + + // Consumes a unary operation expression. + UnaryOpExpression* readUnaryOpExpression(); + + // Consumes a number expression. + NumberExpression* readNumberExpression(); + + // Consumes the decimal component of a number and returns its value, or 0 + // if there is no decimal component. If [allowTrailingDot] is `false`, this + // will throw an error if there's a dot without any numbers following it. + // Otherwise, it will ignore the dot without consuming it. + double tryDecimal(bool allowTrailingDot = false); + + // Consumes the exponent component of a number and returns + // its value, or 1 if there is no exponent component. + double tryExponent(); + + // Consumes a unicode range expression. + StringExpression* readUnicodeRange(); + + // Consumes a variable expression. + VariableExpression* readVariableExpression(bool hoist = true); + + // Consumes a parent selector expression. + SelectorExpression* readParentExpression(); + + // Consumes a quoted string expression. + StringExpression* readInterpolatedString(); + + // Consumes an expression that starts like an identifier. + virtual Expression* readIdentifierLike(); + + // If [name] is the name of a function with special syntax, consumes it. + // Otherwise, returns `null`. [start] is the location before the beginning of [name]. + StringExpression* trySpecialFunction(sass::string name, const Offset& start); + + // Consumes the contents of a plain-CSS `min()` or `max()` function into + // [buffer] if one is available. Returns whether this succeeded. If [allowComma] + // is `true` (the default), this allows `CalcValue` productions separated by commas. + bool tryMinMaxContents(InterpolationBuffer& buffer, bool allowComma = true); + + // Consumes a function named [name] containing an optional `InterpolatedDeclarationValue` + // and adds its text to [buffer]. Returns whether such a function could be consumed. + bool tryMinMaxFunction(InterpolationBuffer& buffer, sass::string name = ""); + + // Like [_urlContents], but returns `null` if the URL fails to parse. + // [start] is the position before the beginning of the name. + // [name] is the function's name; it defaults to `"url"`. + Interpolation* tryUrlContents(const Offset& start, sass::string name = ""); + + // Consumes a [url] token that's allowed to contain SassScript. + // Returns either a `StringExpression` or a `FunctionExpression` + Expression* readFunctionOrStringExpression(); + + // Consumes tokens up to "{", "}", ";", or "!". + // This respects string and comment boundaries and supports interpolation. + // Once this interpolation is evaluated, it's expected to be re-parsed. + // Differences from [parseInterpolatedDeclarationValue] include: + // * This does not balance brackets. + // * This does not interpret backslashes, since + // the text is expected to be re-parsed. + // * This supports Sass-style single-line comments. + // * This does not compress adjacent whitespace characters. + Interpolation* readAlmostAnyValue(bool omitComments = false); + + // Consumes tokens until it reaches a top-level `";"`, `")"`, `"]"`, or `"}"` and + // returns their contents as a string. If [allowEmpty] is `false` (the default), this + // requires at least one token. Unlike [declarationValue], this allows interpolation. + Interpolation* readInterpolatedDeclarationValue(bool allowEmpty = false, + bool allowSemicolon = false, bool allowColon = true); + + // Consumes an identifier that may contain interpolation. + Interpolation* readInterpolatedIdentifier(); + + void consumeInterpolatedIdentifierBody(InterpolationBuffer& buffer); + + // Consumes interpolation. + Expression* readSingleInterpolation(); + + // Consumes a list of media queries. + Interpolation* readMediaQueryList(); + + void readMediaInParens(InterpolationBuffer& buffer); + + void RediaOrInterp(InterpolationBuffer& buffer); + + void readMediaLogicSequence(InterpolationBuffer& buffer, sass::string op); + + void readMediaOrInterp(InterpolationBuffer& buffer); + + // Consumes a single media query and appends it to [buffer]. + void readMediaQuery(InterpolationBuffer& buffer); + + // Consumes a media query feature. + Interpolation* readMediaFeature(); + + // Returns `false` until the scanner reaches a + // top-level `<`, `>`, or a `=` that's not `==`. + bool lookingAtExpressionEnd(); + + // Consumes an expression until it reaches a + // top-level `<`, `>`, or a `=` that's not `==`. + Expression* readExpressionUntilComparison(); + + // Consumes a `@supports` condition. + SupportsCondition* readSupportsCondition(); + + // Consumes a parenthesized supports condition, or an interpolation. + SupportsCondition* readSupportsConditionInParens(); + + // Tries to consume a negated supports condition. + // Returns `nullptr` if it fails. + SupportsNegation* trySupportsNegation(); + + SupportsDeclaration* readSupportsDeclarationValue(Expression* name, Offset& start); + + SupportsOperation* trySupportsOperation(Interpolation* interpolation, Offset& start); + + // Like [identifier], but rejects identifiers that begin with `_` or `-`. + sass::string readPublicIdentifier(); + + // Returns whether the scanner is immediately before an identifier that may contain + // interpolation. This is based on the CSS algorithm, but it assumes all backslashes + // start escapes and it considers interpolation to be valid in an identifier. + // https://drafts.csswg.org/css-syntax-3/#would-start-an-identifier + bool lookingAtInterpolatedIdentifier() const; + + // Returns whether the scanner is immediately before a sequence + // of characters that could be part of an CSS identifier body. + // The identifier body may include interpolation. + bool lookingAtInterpolatedIdentifierBody() const; + + // Returns whether the scanner is immediately before a SassScript expression. + bool lookingAtExpression() const; + + // Consumes a block of [child] statements and passes them, as well as + // the span from [start] to the end of the child block, to [create]. + template + T* withChildren(Statement* (StylesheetParser::* child)(), + const Offset& start, Args... args) + { + StatementVector elements(readChildren(child)); + SharedPtr result = SASS_MEMORY_NEW(T, + scanner.relevantSpanFrom(start), + args..., std::move(elements)); + scanWhitespaceWithoutComments(); + return result.detach(); + } + + }; + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + +} + +#endif diff --git a/src/permutate.hpp b/src/permutate.hpp index 387704f510..0c285fe079 100644 --- a/src/permutate.hpp +++ b/src/permutate.hpp @@ -1,6 +1,14 @@ -#ifndef SASS_PATHS_H -#define SASS_PATHS_H +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#ifndef SASS_PERMUTATE_HPP +#define SASS_PERMUTATE_HPP +// sass.hpp must go before all system headers +// to get the __EXTENSIONS__ fix on Solaris. +#include "capi_sass.hpp" + +#include #include namespace Sass { @@ -45,7 +53,7 @@ namespace Sass { sass::vector perm; // Create one permutation for state for (size_t i = 0; i < L; i += 1) { - perm.push_back(in.at(i).at(in[i].size() - state[i] - 1)); + perm.emplace_back(in.at(i).at(in[i].size() - state[i] - 1)); } // Current group finished if (state[n] == 0) { @@ -53,7 +61,7 @@ namespace Sass { while (n < L && state[++n] == 0) {} if (n == L) { - out.push_back(perm); + out.emplace_back(perm); break; } @@ -70,7 +78,7 @@ namespace Sass { else { state[n] -= 1; } - out.push_back(perm); + out.emplace_back(perm); } delete[] state; @@ -95,8 +103,8 @@ namespace Sass { // ``` // template - sass::vector> - permutateAlt(const sass::vector>& in) { + sass::vector> permutateAlt( + const sass::vector>& in) { size_t L = in.size(); size_t n = in.size() - 1; @@ -125,7 +133,7 @@ namespace Sass { sass::vector perm; // Create one permutation for state for (size_t i = 0; i < L; i += 1) { - perm.push_back(in.at(i).at(in[i].size() - state[i] - 1)); + perm.emplace_back(in.at(i).at(in[i].size() - state[i] - 1)); } // Current group finished if (state[n] == 0) { @@ -144,14 +152,14 @@ namespace Sass { n = L - 1; } else { - out.push_back(perm); + out.emplace_back(perm); break; } } else { state[n] -= 1; } - out.push_back(perm); + out.emplace_back(perm); } delete[] state; diff --git a/src/plugins.cpp b/src/plugins.cpp index e5c744ff29..441aef84d7 100644 --- a/src/plugins.cpp +++ b/src/plugins.cpp @@ -1,11 +1,13 @@ -// sass.hpp must go before all system headers to get the -// __EXTENSIONS__ fix on Solaris. -#include "sass.hpp" - -#include -#include "output.hpp" +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ #include "plugins.hpp" -#include "util.hpp" + +#include +#include +#include "unicode.hpp" +#include "compiler.hpp" +#include "string_utils.hpp" #ifdef _WIN32 #include @@ -18,26 +20,18 @@ namespace Sass { - Plugins::Plugins(void) { } - Plugins::~Plugins(void) - { - for (auto function : functions) { - sass_delete_function(function); - } - for (auto importer : importers) { - sass_delete_importer(importer); - } - for (auto header : headers) { - sass_delete_importer(header); - } - } + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // Constructor + Plugins::Plugins(Compiler& compiler) : + compiler(compiler) {} // check if plugin is compatible with this version // plugins may be linked static against libsass // we try to be compatible between major versions inline bool compatibility(const char* their_version) { -// const char* their_version = "3.1.2"; // first check if anyone has an unknown version const char* our_version = libsass_version(); if (!strcmp(their_version, "[na]")) return false; @@ -47,7 +41,7 @@ namespace Sass { size_t pos = sass::string(our_version).find('.', 0); if (pos != sass::string::npos) pos = sass::string(our_version).find('.', pos + 1); - // if we do not have two dots we fallback to compare complete string + // if we do not have two dots we fall back to compare complete string if (pos == sass::string::npos) { return strcmp(their_version, our_version) ? 0 : 1; } // otherwise only compare up to the second dot (major versions) else { return strncmp(their_version, our_version, pos) ? 0 : 1; } @@ -59,53 +53,50 @@ namespace Sass { { typedef const char* (*__plugin_version__)(void); - typedef Sass_Function_List (*__plugin_load_fns__)(void); - typedef Sass_Importer_List (*__plugin_load_imps__)(void); + typedef const char* (*__plugin_set_seed__)(uint32_t); + // typedef struct SassFunctionList* (*__plugin_load_fns__)(void); + // typedef struct SassImporterList* (*__plugin_load_imps__)(void); + + typedef void(*__plugin_init__)(struct SassCompiler* compiler); + + // Pass seed by env variable until further notice + sass::sstream strm; strm << getHashSeed(); + // SET_ENV("SASS_HASH_SEED", strm.str().c_str()); if (LOAD_LIB(plugin, path)) { - // try to load initial function to query libsass version suppor + // try to load initial function to query libsass version support if (LOAD_LIB_FN(__plugin_version__, plugin_version, "libsass_get_version")) { // get the libsass version of the plugin if (!compatibility(plugin_version())) return false; - // try to get import address for "libsass_load_functions" - if (LOAD_LIB_FN(__plugin_load_fns__, plugin_load_functions, "libsass_load_functions")) + // try to get import address for "plugin_set_seed_function" + if (LOAD_LIB_FN(__plugin_set_seed__, plugin_set_seed_function, "libsass_set_seed_function")) { - Sass_Function_List fns = plugin_load_functions(), _p = fns; - while (fns && *fns) { functions.push_back(*fns); ++ fns; } - sass_free_memory(_p); // only delete the container, items not yet + plugin_set_seed_function(getHashSeed()); } - // try to get import address for "libsass_load_importers" - if (LOAD_LIB_FN(__plugin_load_imps__, plugin_load_importers, "libsass_load_importers")) - { - Sass_Importer_List imps = plugin_load_importers(), _p = imps; - while (imps && *imps) { importers.push_back(*imps); ++ imps; } - sass_free_memory(_p); // only delete the container, items not yet - } - // try to get import address for "libsass_load_headers" - if (LOAD_LIB_FN(__plugin_load_imps__, plugin_load_headers, "libsass_load_headers")) + + // try to get import address for "plugin_init" function + if (LOAD_LIB_FN(__plugin_init__, plugin_init_function, "libsass_init_plugin")) { - Sass_Importer_List imps = plugin_load_headers(), _p = imps; - while (imps && *imps) { headers.push_back(*imps); ++ imps; } - sass_free_memory(_p); // only delete the container, items not yet + plugin_init_function(compiler.wrap()); } - // success + return true; } else { // print debug message to stderr (should not happen) - std::cerr << "failed loading 'libsass_support' in <" << path << ">" << std::endl; - if (const char* dlsym_error = dlerror()) std::cerr << dlsym_error << std::endl; + std::cerr << "failed loading 'libsass_support' in <" << path << ">" << STRMLF; + if (const char* dlsym_error = dlerror()) std::cerr << dlsym_error << STRMLF; CLOSE_LIB(plugin); } } else { // print debug message to stderr (should not happen) - std::cerr << "failed loading plugin <" << path << ">" << std::endl; - if (const char* dlopen_error = dlerror()) std::cerr << dlopen_error << std::endl; + std::cerr << "failed loading plugin <" << path << ">" << STRMLF; + if (const char* dlopen_error = dlerror()) std::cerr << dlopen_error << STRMLF; } return false; @@ -128,7 +119,7 @@ namespace Sass { // trailing slash is guaranteed sass::string globsrch(path + "*.dll"); // convert to wide chars (utf16) for system call - std::wstring wglobsrch(UTF_8::convert_to_utf16(globsrch)); + sass::wstring wglobsrch(Unicode::utf8to16(globsrch)); HANDLE hFile = FindFirstFileW(wglobsrch.c_str(), &data); // check if system called returned a result // ToDo: maybe we should print a debug message @@ -140,9 +131,9 @@ namespace Sass { try { // the system will report the filenames with wide chars (utf16) - sass::string entry = UTF_8::convert_from_utf16(data.cFileName); + sass::string entry = Unicode::utf16to8(data.cFileName); // check if file ending matches exactly - if (!ends_with(entry, ".dll")) continue; + if (!StringUtils::endsWith(entry, ".dll", 4)) continue; // load the plugin and increase counter if (load_plugin(path + entry)) ++ loaded; // check if there should be more entries @@ -154,7 +145,7 @@ namespace Sass { { // report the error to the console (should not happen) // seems like we got strange data from the system call? - std::cerr << "filename in plugin path has invalid utf8?" << std::endl; + std::cerr << "filename in plugin path has invalid utf8?" << STRMLF; } } } @@ -162,19 +153,20 @@ namespace Sass { { // report the error to the console (should not happen) // implementors should make sure to provide valid utf8 - std::cerr << "plugin path contains invalid utf8" << std::endl; + std::cerr << "plugin path contains invalid utf8" << STRMLF; } #else DIR *dp; struct dirent *dirp; + using namespace StringUtils; if((dp = opendir(path.c_str())) == NULL) return -1; while ((dirp = readdir(dp)) != NULL) { #if __APPLE__ - if (!ends_with(dirp->d_name, ".dylib")) continue; + if (!endsWithIgnoreCase(dirp->d_name, ".dylib", 6)) continue; #else - if (!ends_with(dirp->d_name, ".so")) continue; + if (!endsWithIgnoreCase(dirp->d_name, ".so", 3)) continue; #endif if (load_plugin(path + dirp->d_name)) ++ loaded; } diff --git a/src/plugins.hpp b/src/plugins.hpp index c600df45d2..dca5251c26 100644 --- a/src/plugins.hpp +++ b/src/plugins.hpp @@ -1,16 +1,20 @@ -#ifndef SASS_PLUGINS_H -#define SASS_PLUGINS_H +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#ifndef SASS_PLUGINS_HPP +#define SASS_PLUGINS_HPP -#include -#include -#include "utf8_string.hpp" -#include "sass/functions.h" +// sass.hpp must go before all system headers +// to get the __EXTENSIONS__ fix on Solaris. +#include "capi_sass.hpp" + +#include "ast_fwd_decl.hpp" #ifdef _WIN32 - #define LOAD_LIB(var, path) HMODULE var = LoadLibraryW(UTF_8::convert_to_utf16(path).c_str()) + #define LOAD_LIB(var, path) HMODULE var = LoadLibraryW(Unicode::utf8to16(path).c_str()) #define LOAD_LIB_WCHR(var, path_wide_str) HMODULE var = LoadLibraryW(path_wide_str.c_str()) - #define LOAD_LIB_FN(type, var, name) type var = (type) GetProcAddress(plugin, name) + #define LOAD_LIB_FN(type, var, name) type var = (type)(void*)GetProcAddress(plugin, name) #define CLOSE_LIB(var) FreeLibrary(var) #ifndef dlerror @@ -27,28 +31,19 @@ namespace Sass { - class Plugins { - public: // c-tor - Plugins(void); - ~Plugins(void); - - public: // methods - // load one specific plugin - bool load_plugin(const sass::string& path); - // load all plugins from a directory - size_t load_plugins(const sass::string& path); - - public: // public accessors - const sass::vector get_headers(void) { return headers; } - const sass::vector get_importers(void) { return importers; } - const sass::vector get_functions(void) { return functions; } - - private: // private vars - sass::vector headers; - sass::vector importers; - sass::vector functions; + private: + // Associated compiler + Compiler& compiler; + + public: + // Value constructor + Plugins(Compiler& compiler); + // Load one specific plugin + bool load_plugin(const sass::string& path); + // Load all plugins from a directory + size_t load_plugins(const sass::string& path); }; diff --git a/src/position.cpp b/src/position.cpp deleted file mode 100644 index 990185332a..0000000000 --- a/src/position.cpp +++ /dev/null @@ -1,163 +0,0 @@ -// sass.hpp must go before all system headers to get the -// __EXTENSIONS__ fix on Solaris. -#include "sass.hpp" - -#include "position.hpp" -#include "source.hpp" - -namespace Sass { - - - Offset::Offset(const char chr) - : line(chr == '\n' ? 1 : 0), - column(chr == '\n' ? 0 : 1) - {} - - Offset::Offset(const char* string) - : line(0), column(0) - { - *this = inc(string, string + strlen(string)); - } - - Offset::Offset(const sass::string& text) - : line(0), column(0) - { - *this = inc(text.c_str(), text.c_str() + text.size()); - } - - Offset::Offset(const size_t line, const size_t column) - : line(line), column(column) { } - - // init/create instance from const char substring - Offset Offset::init(const char* beg, const char* end) - { - Offset offset(0, 0); - if (end == 0) { - end = beg + strlen(beg); - } - offset.add(beg, end); - return offset; - } - - // increase offset by given string (mostly called by lexer) - // increase line counter and count columns on the last line - Offset Offset::add(const char* begin, const char* end) - { - if (end == 0) return *this; - while (begin < end && *begin) { - if (*begin == '\n') { - ++ line; - // start new line - column = 0; - } else { - // do not count any utf8 continuation bytes - // https://stackoverflow.com/a/9356203/1550314 - // https://en.wikipedia.org/wiki/UTF-8#Description - unsigned char chr = *begin; - // Ignore all `10xxxxxx` chars - // '0xxxxxxx' are ASCII chars - // '11xxxxxx' are utf8 starts - // 64 => initial utf8 byte - // 128 => regular ASCII char - if ((chr & 192) != 128) { - // regular ASCII char - column += 1; - } - } - ++ begin; - } - return *this; - } - - // increase offset by given string (mostly called by lexer) - // increase line counter and count columns on the last line - Offset Offset::inc(const char* begin, const char* end) const - { - Offset offset(line, column); - offset.add(begin, end); - return offset; - } - - bool Offset::operator== (const Offset &pos) const - { - return line == pos.line && column == pos.column; - } - - bool Offset::operator!= (const Offset &pos) const - { - return line != pos.line || column != pos.column; - } - - void Offset::operator+= (const Offset &off) - { - *this = Offset(line + off.line, off.line > 0 ? off.column : column + off.column); - } - - Offset Offset::operator+ (const Offset &off) const - { - return Offset(line + off.line, off.line > 0 ? off.column : column + off.column); - } - - Offset Offset::operator- (const Offset &off) const - { - return Offset(line - off.line, off.line == line ? column - off.column : column); - } - - Position::Position(const size_t file) - : Offset(0, 0), file(file) { } - - Position::Position(const size_t file, const Offset& offset) - : Offset(offset), file(file) { } - - Position::Position(const size_t line, const size_t column) - : Offset(line, column), file(-1) { } - - Position::Position(const size_t file, const size_t line, const size_t column) - : Offset(line, column), file(file) { } - - - SourceSpan::SourceSpan(const char* path) - : source(SASS_MEMORY_NEW(SynthFile, path)), position(0, 0), offset(0, 0) { } - - SourceSpan::SourceSpan(SourceDataObj source, const Offset& position, const Offset& offset) - : source(source), position(position), offset(offset) { } - - Position Position::add(const char* begin, const char* end) - { - Offset::add(begin, end); - return *this; - } - - Position Position::inc(const char* begin, const char* end) const - { - Offset offset(line, column); - offset = offset.inc(begin, end); - return Position(file, offset); - } - - bool Position::operator== (const Position &pos) const - { - return file == pos.file && line == pos.line && column == pos.column; - } - - bool Position::operator!= (const Position &pos) const - { - return file == pos.file || line != pos.line || column != pos.column; - } - - void Position::operator+= (const Offset &off) - { - *this = Position(file, line + off.line, off.line > 0 ? off.column : column + off.column); - } - - const Position Position::operator+ (const Offset &off) const - { - return Position(file, line + off.line, off.line > 0 ? off.column : column + off.column); - } - - const Offset Position::operator- (const Offset &off) const - { - return Offset(line - off.line, off.line == line ? column - off.column : column); - } - -} diff --git a/src/position.hpp b/src/position.hpp deleted file mode 100644 index 45d78a0494..0000000000 --- a/src/position.hpp +++ /dev/null @@ -1,147 +0,0 @@ -#ifndef SASS_POSITION_H -#define SASS_POSITION_H - -#include -#include -#include "source_data.hpp" -#include "ast_fwd_decl.hpp" - -namespace Sass { - - - class Offset { - - public: // c-tor - Offset(const char chr); - Offset(const char* string); - Offset(const sass::string& text); - Offset(const size_t line, const size_t column); - - // return new position, incremented by the given string - Offset add(const char* begin, const char* end); - Offset inc(const char* begin, const char* end) const; - - // init/create instance from const char substring - static Offset init(const char* beg, const char* end); - - public: // overload operators for position - void operator+= (const Offset &pos); - bool operator== (const Offset &pos) const; - bool operator!= (const Offset &pos) const; - Offset operator+ (const Offset &off) const; - Offset operator- (const Offset &off) const; - - public: // overload output stream operator - // friend std::ostream& operator<<(std::ostream& strm, const Offset& off); - - public: - Offset off() { return *this; } - - public: - size_t line; - size_t column; - - }; - - class Position : public Offset { - - public: // c-tor - Position(const size_t file); // line(0), column(0) - Position(const size_t file, const Offset& offset); - Position(const size_t line, const size_t column); // file(-1) - Position(const size_t file, const size_t line, const size_t column); - - public: // overload operators for position - void operator+= (const Offset &off); - bool operator== (const Position &pos) const; - bool operator!= (const Position &pos) const; - const Position operator+ (const Offset &off) const; - const Offset operator- (const Offset &off) const; - // return new position, incremented by the given string - Position add(const char* begin, const char* end); - Position inc(const char* begin, const char* end) const; - - public: // overload output stream operator - // friend std::ostream& operator<<(std::ostream& strm, const Position& pos); - - public: - size_t file; - - }; - - // Token type for representing lexed chunks of text - class Token { - public: - const char* prefix; - const char* begin; - const char* end; - - Token() - : prefix(0), begin(0), end(0) { } - Token(const char* b, const char* e) - : prefix(b), begin(b), end(e) { } - Token(const char* str) - : prefix(str), begin(str), end(str + strlen(str)) { } - Token(const char* p, const char* b, const char* e) - : prefix(p), begin(b), end(e) { } - - size_t length() const { return end - begin; } - sass::string ws_before() const { return sass::string(prefix, begin); } - sass::string to_string() const { return sass::string(begin, end); } - sass::string time_wspace() const { - sass::string str(to_string()); - sass::string whitespaces(" \t\f\v\n\r"); - return str.erase(str.find_last_not_of(whitespaces)+1); - } - - operator bool() { return begin && end && begin >= end; } - operator sass::string() { return to_string(); } - - bool operator==(Token t) { return to_string() == t.to_string(); } - }; - - class SourceSpan { - - public: - - SourceSpan(const char* path); - - SourceSpan(SourceDataObj source, - const Offset& position = Offset(0, 0), - const Offset& offset = Offset(0, 0)); - - const char* getPath() const { - return source->getPath(); - } - - const char* getRawData() const { - return source->getRawData(); - } - - Offset getPosition() const { - return position; - } - - size_t getLine() const { - return position.line + 1; - } - - size_t getColumn() const { - return position.column + 1; - } - - size_t getSrcId() const { - return source == nullptr - ? std::string::npos - : source->getSrcId(); - } - - SourceDataObj source; - Offset position; - Offset offset; - - }; - -} - -#endif diff --git a/src/prelexer.cpp b/src/prelexer.cpp deleted file mode 100644 index 497fd69bff..0000000000 --- a/src/prelexer.cpp +++ /dev/null @@ -1,1780 +0,0 @@ -// sass.hpp must go before all system headers to get the -// __EXTENSIONS__ fix on Solaris. -#include "sass.hpp" - -#include -#include -#include "util.hpp" -#include "util_string.hpp" -#include "position.hpp" -#include "prelexer.hpp" -#include "constants.hpp" - - -namespace Sass { - // using namespace Lexer; - using namespace Constants; - - namespace Prelexer { - - - /* - - def string_re(open, close) - /#{open}((?:\\.|\#(?!\{)|[^#{close}\\#])*)(#{close}|#\{)/m - end - end - - # A hash of regular expressions that are used for tokenizing strings. - # - # The key is a `[Symbol, Boolean]` pair. - # The symbol represents which style of quotation to use, - # while the boolean represents whether or not the string - # is following an interpolated segment. - STRING_REGULAR_EXPRESSIONS = { - :double => { - /#{open}((?:\\.|\#(?!\{)|[^#{close}\\#])*)(#{close}|#\{)/m - false => string_re('"', '"'), - true => string_re('', '"') - }, - :single => { - false => string_re("'", "'"), - true => string_re('', "'") - }, - :uri => { - false => /url\(#{W}(#{URLCHAR}*?)(#{W}\)|#\{)/, - true => /(#{URLCHAR}*?)(#{W}\)|#\{)/ - }, - # Defined in https://developer.mozilla.org/en/CSS/@-moz-document as a - # non-standard version of http://www.w3.org/TR/css3-conditional/ - :url_prefix => { - false => /url-prefix\(#{W}(#{URLCHAR}*?)(#{W}\)|#\{)/, - true => /(#{URLCHAR}*?)(#{W}\)|#\{)/ - }, - :domain => { - false => /domain\(#{W}(#{URLCHAR}*?)(#{W}\)|#\{)/, - true => /(#{URLCHAR}*?)(#{W}\)|#\{)/ - } - } - */ - - /* - /#{open} - ( - \\. - | - \# (?!\{) - | - [^#{close}\\#] - )* - (#{close}|#\{) - /m - false => string_re('"', '"'), - true => string_re('', '"') - */ - extern const char string_double_negates[] = "\"\\#"; - const char* re_string_double_close(const char* src) - { - return sequence < - // valid chars - zero_plus < - alternatives < - // escaped char - sequence < - exactly <'\\'>, - any_char - >, - // non interpolate hash - sequence < - exactly <'#'>, - negate < - exactly <'{'> - > - >, - // other valid chars - neg_class_char < - string_double_negates - > - > - >, - // quoted string closer - // or interpolate opening - alternatives < - exactly <'"'>, - lookahead < exactly< hash_lbrace > > - > - >(src); - } - - const char* re_string_double_open(const char* src) - { - return sequence < - // quoted string opener - exactly <'"'>, - // valid chars - zero_plus < - alternatives < - // escaped char - sequence < - exactly <'\\'>, - any_char - >, - // non interpolate hash - sequence < - exactly <'#'>, - negate < - exactly <'{'> - > - >, - // other valid chars - neg_class_char < - string_double_negates - > - > - >, - // quoted string closer - // or interpolate opening - alternatives < - exactly <'"'>, - lookahead < exactly< hash_lbrace > > - > - >(src); - } - - extern const char string_single_negates[] = "'\\#"; - const char* re_string_single_close(const char* src) - { - return sequence < - // valid chars - zero_plus < - alternatives < - // escaped char - sequence < - exactly <'\\'>, - any_char - >, - // non interpolate hash - sequence < - exactly <'#'>, - negate < - exactly <'{'> - > - >, - // other valid chars - neg_class_char < - string_single_negates - > - > - >, - // quoted string closer - // or interpolate opening - alternatives < - exactly <'\''>, - lookahead < exactly< hash_lbrace > > - > - >(src); - } - - const char* re_string_single_open(const char* src) - { - return sequence < - // quoted string opener - exactly <'\''>, - // valid chars - zero_plus < - alternatives < - // escaped char - sequence < - exactly <'\\'>, - any_char - >, - // non interpolate hash - sequence < - exactly <'#'>, - negate < - exactly <'{'> - > - >, - // other valid chars - neg_class_char < - string_single_negates - > - > - >, - // quoted string closer - // or interpolate opening - alternatives < - exactly <'\''>, - lookahead < exactly< hash_lbrace > > - > - >(src); - } - - /* - :uri => { - false => /url\(#{W}(#{URLCHAR}*?)(#{W}\)|#\{)/, - true => /(#{URLCHAR}*?)(#{W}\)|#\{)/ - }, - */ - const char* re_string_uri_close(const char* src) - { - return sequence < - non_greedy< - alternatives< - class_char< real_uri_chars >, - uri_character, - NONASCII, - ESCAPE - >, - alternatives< - sequence < optional < W >, exactly <')'> >, - lookahead < exactly< hash_lbrace > > - > - >, - optional < - sequence < optional < W >, exactly <')'> > - > - >(src); - } - - const char* re_string_uri_open(const char* src) - { - return sequence < - exactly <'u'>, - exactly <'r'>, - exactly <'l'>, - exactly <'('>, - W, - alternatives< - quoted_string, - non_greedy< - alternatives< - class_char< real_uri_chars >, - uri_character, - NONASCII, - ESCAPE - >, - alternatives< - sequence < W, exactly <')'> >, - exactly< hash_lbrace > - > - > - > - >(src); - } - - // Match a line comment (/.*?(?=\n|\r\n?|\f|\Z)/. - const char* line_comment(const char* src) - { - return sequence< - exactly < - slash_slash - >, - non_greedy< - any_char, - end_of_line - > - >(src); - } - - // Match a block comment. - const char* block_comment(const char* src) - { - return sequence< - delimited_by< - slash_star, - star_slash, - false - > - >(src); - } - /* not use anymore - remove? - const char* block_comment_prefix(const char* src) { - return exactly(src); - } - // Match either comment. - const char* comment(const char* src) { - return line_comment(src); - } - */ - - // Match zero plus white-space or line_comments - const char* optional_css_whitespace(const char* src) { - return zero_plus< alternatives >(src); - } - const char* css_whitespace(const char* src) { - return one_plus< alternatives >(src); - } - // Match optional_css_whitespace plus block_comments - const char* optional_css_comments(const char* src) { - return zero_plus< alternatives >(src); - } - const char* css_comments(const char* src) { - return one_plus< alternatives >(src); - } - - // Match one backslash escaped char /\\./ - const char* escape_seq(const char* src) - { - return sequence< - exactly<'\\'>, - alternatives < - minmax_range< - 1, 3, xdigit - >, - any_char - >, - optional < - exactly <' '> - > - >(src); - } - - // Match identifier start - const char* identifier_alpha(const char* src) - { - return alternatives< - unicode_seq, - alpha, - nonascii, - exactly<'-'>, - exactly<'_'>, - NONASCII, - ESCAPE, - escape_seq - >(src); - } - - // Match identifier after start - const char* identifier_alnum(const char* src) - { - return alternatives< - unicode_seq, - alnum, - nonascii, - exactly<'-'>, - exactly<'_'>, - NONASCII, - ESCAPE, - escape_seq - >(src); - } - - // Match CSS identifiers. - const char* strict_identifier(const char* src) - { - return sequence< - one_plus < strict_identifier_alpha >, - zero_plus < strict_identifier_alnum > - // word_boundary not needed - >(src); - } - - // Match CSS identifiers. - const char* identifier(const char* src) - { - return sequence< - zero_plus< exactly<'-'> >, - one_plus < identifier_alpha >, - zero_plus < identifier_alnum > - // word_boundary not needed - >(src); - } - - const char* strict_identifier_alpha(const char* src) - { - return alternatives < - alpha, - nonascii, - escape_seq, - exactly<'_'> - >(src); - } - - const char* strict_identifier_alnum(const char* src) - { - return alternatives < - alnum, - nonascii, - escape_seq, - exactly<'_'> - >(src); - } - - // Match a single CSS unit - const char* one_unit(const char* src) - { - return sequence < - optional < exactly <'-'> >, - strict_identifier_alpha, - zero_plus < alternatives< - strict_identifier_alnum, - sequence < - one_plus < exactly<'-'> >, - strict_identifier_alpha - > - > > - >(src); - } - - // Match numerator/denominator CSS units - const char* multiple_units(const char* src) - { - return - sequence < - one_unit, - zero_plus < - sequence < - exactly <'*'>, - one_unit - > - > - >(src); - } - - // Match complex CSS unit identifiers - const char* unit_identifier(const char* src) - { - return sequence < - multiple_units, - optional < - sequence < - exactly <'/'>, - negate < sequence < - exactly < calc_fn_kwd >, - exactly < '(' > - > >, - multiple_units - > > - >(src); - } - - const char* identifier_alnums(const char* src) - { - return one_plus< identifier_alnum >(src); - } - - // Match number prefix ([\+\-]+) - const char* number_prefix(const char* src) { - return alternatives < - exactly < '+' >, - sequence < - exactly < '-' >, - optional_css_whitespace, - exactly< '-' > - > - >(src); - } - - // Match interpolant schemas - const char* identifier_schema(const char* src) { - - return sequence < - one_plus < - sequence < - zero_plus < - alternatives < - sequence < - optional < - exactly <'$'> - >, - identifier - >, - exactly <'-'> - > - >, - interpolant, - zero_plus < - alternatives < - digits, - sequence < - optional < - exactly <'$'> - >, - identifier - >, - quoted_string, - exactly<'-'> - > - > - > - >, - negate < - exactly<'%'> - > - > (src); - } - - // interpolants can be recursive/nested - const char* interpolant(const char* src) { - return recursive_scopes< exactly, exactly >(src); - } - - // $re_squote = /'(?:$re_itplnt|\\.|[^'])*'/ - const char* single_quoted_string(const char* src) { - // match a single quoted string, while skipping interpolants - return sequence < - exactly <'\''>, - zero_plus < - alternatives < - // skip escapes - sequence < - exactly < '\\' >, - re_linebreak - >, - escape_seq, - unicode_seq, - // skip interpolants - interpolant, - // skip non delimiters - any_char_but < '\'' > - > - >, - exactly <'\''> - >(src); - } - - // $re_dquote = /"(?:$re_itp|\\.|[^"])*"/ - const char* double_quoted_string(const char* src) { - // match a single quoted string, while skipping interpolants - return sequence < - exactly <'"'>, - zero_plus < - alternatives < - // skip escapes - sequence < - exactly < '\\' >, - re_linebreak - >, - escape_seq, - unicode_seq, - // skip interpolants - interpolant, - // skip non delimiters - any_char_but < '"' > - > - >, - exactly <'"'> - >(src); - } - - // $re_quoted = /(?:$re_squote|$re_dquote)/ - const char* quoted_string(const char* src) { - // match a quoted string, while skipping interpolants - return alternatives< - single_quoted_string, - double_quoted_string - >(src); - } - - const char* sass_value(const char* src) { - return alternatives < - quoted_string, - identifier, - percentage, - hex, - dimension, - number - >(src); - } - - // this is basically `one_plus < sass_value >` - // takes care to not parse invalid combinations - const char* value_combinations(const char* src) { - // `2px-2px` is invalid combo - bool was_number = false; - const char* pos; - while (src) { - if ((pos = alternatives < quoted_string, identifier, percentage, hex >(src))) { - was_number = false; - src = pos; - } else if (!was_number && !exactly<'+'>(src) && (pos = alternatives < dimension, number >(src))) { - was_number = true; - src = pos; - } else { - break; - } - } - return src; - } - - // must be at least one interpolant - // can be surrounded by sass values - // make sure to never parse (dim)(dim) - // since this wrongly consumes `2px-1px` - // `2px1px` is valid number (unit `px1px`) - const char* value_schema(const char* src) - { - return sequence < - one_plus < - sequence < - optional < value_combinations >, - interpolant, - optional < value_combinations > - > - > - >(src); - } - - // Match CSS '@' keywords. - const char* at_keyword(const char* src) { - return sequence, identifier>(src); - } - - /* - tok(%r{ - ( - \\. - | - (?!url\() - [^"'/\#!;\{\}] # " - | - /(?![\*\/]) - | - \#(?!\{) - | - !(?![a-z]) # TODO: never consume "!" when issue 1126 is fixed. - )+ - }xi) || tok(COMMENT) || tok(SINGLE_LINE_COMMENT) || interp_string || interp_uri || - interpolation(:warn_for_color) - */ - const char* re_almost_any_value_token(const char* src) { - - return alternatives < - one_plus < - alternatives < - sequence < - exactly <'\\'>, - any_char - >, - sequence < - negate < - uri_prefix - >, - neg_class_char < - almost_any_value_class - > - >, - sequence < - exactly <'/'>, - negate < - alternatives < - exactly <'/'>, - exactly <'*'> - > - > - >, - sequence < - exactly <'\\'>, - exactly <'#'>, - negate < - exactly <'{'> - > - >, - sequence < - exactly <'!'>, - negate < - alpha - > - > - > - >, - block_comment, - line_comment, - interpolant, - space, - sequence < - exactly<'u'>, - exactly<'r'>, - exactly<'l'>, - exactly<'('>, - zero_plus < - alternatives < - class_char< real_uri_chars >, - uri_character, - NONASCII, - ESCAPE - > - >, - // false => /url\(#{W}(#{URLCHAR}*?)(#{W}\)|#\{)/, - // true => /(#{URLCHAR}*?)(#{W}\)|#\{)/ - exactly<')'> - > - >(src); - } - - /* - DIRECTIVES = Set[:mixin, :include, :function, :return, :debug, :warn, :for, - :each, :while, :if, :else, :extend, :import, :media, :charset, :content, - :_moz_document, :at_root, :error] - */ - const char* re_special_directive(const char* src) { - return alternatives < - word < mixin_kwd >, - word < include_kwd >, - word < function_kwd >, - word < return_kwd >, - word < debug_kwd >, - word < warn_kwd >, - word < for_kwd >, - word < each_kwd >, - word < while_kwd >, - word < if_kwd >, - word < else_kwd >, - word < extend_kwd >, - word < import_kwd >, - word < media_kwd >, - word < charset_kwd >, - word < content_kwd >, - // exactly < moz_document_kwd >, - word < at_root_kwd >, - word < error_kwd > - >(src); - } - - const char* re_prefixed_directive(const char* src) { - return sequence < - optional < - sequence < - exactly <'-'>, - one_plus < alnum >, - exactly <'-'> - > - >, - exactly < supports_kwd > - >(src); - } - - const char* re_reference_combinator(const char* src) { - return sequence < - optional < - sequence < - zero_plus < - exactly <'-'> - >, - identifier, - exactly <'|'> - > - >, - zero_plus < - exactly <'-'> - >, - identifier - >(src); - } - - const char* static_reference_combinator(const char* src) { - return sequence < - exactly <'/'>, - re_reference_combinator, - exactly <'/'> - >(src); - } - - const char* schema_reference_combinator(const char* src) { - return sequence < - exactly <'/'>, - optional < - sequence < - css_ip_identifier, - exactly <'|'> - > - >, - css_ip_identifier, - exactly <'/'> - > (src); - } - - const char* kwd_import(const char* src) { - return word(src); - } - - const char* kwd_at_root(const char* src) { - return word(src); - } - - const char* kwd_with_directive(const char* src) { - return word(src); - } - - const char* kwd_without_directive(const char* src) { - return word(src); - } - - const char* kwd_media(const char* src) { - return word(src); - } - - const char* kwd_supports_directive(const char* src) { - return word(src); - } - - const char* kwd_mixin(const char* src) { - return word(src); - } - - const char* kwd_function(const char* src) { - return word(src); - } - - const char* kwd_return_directive(const char* src) { - return word(src); - } - - const char* kwd_include_directive(const char* src) { - return word(src); - } - - const char* kwd_content_directive(const char* src) { - return word(src); - } - - const char* kwd_charset_directive(const char* src) { - return word(src); - } - - const char* kwd_extend(const char* src) { - return word(src); - } - - - const char* kwd_if_directive(const char* src) { - return word(src); - } - - const char* kwd_else_directive(const char* src) { - return word(src); - } - const char* elseif_directive(const char* src) { - return sequence< exactly< else_kwd >, - optional_css_comments, - word< if_after_else_kwd > >(src); - } - - const char* kwd_for_directive(const char* src) { - return word(src); - } - - const char* kwd_from(const char* src) { - return word(src); - } - - const char* kwd_to(const char* src) { - return word(src); - } - - const char* kwd_through(const char* src) { - return word(src); - } - - const char* kwd_each_directive(const char* src) { - return word(src); - } - - const char* kwd_in(const char* src) { - return word(src); - } - - const char* kwd_while_directive(const char* src) { - return word(src); - } - - const char* name(const char* src) { - return one_plus< alternatives< alnum, - exactly<'-'>, - exactly<'_'>, - escape_seq > >(src); - } - - const char* kwd_warn(const char* src) { - return word(src); - } - - const char* kwd_err(const char* src) { - return word(src); - } - - const char* kwd_dbg(const char* src) { - return word(src); - } - - /* not used anymore - remove? - const char* directive(const char* src) { - return sequence< exactly<'@'>, identifier >(src); - } */ - - const char* kwd_null(const char* src) { - return word(src); - } - - const char* css_identifier(const char* src) { - return sequence < - zero_plus < - exactly <'-'> - >, - identifier - >(src); - } - - const char* css_ip_identifier(const char* src) { - return sequence < - zero_plus < - exactly <'-'> - >, - alternatives < - identifier, - interpolant - > - >(src); - } - - // Match CSS type selectors - const char* namespace_prefix(const char* src) { - return sequence < - optional < - alternatives < - exactly <'*'>, - css_identifier - > - >, - exactly <'|'>, - negate < - exactly <'='> - > - >(src); - } - - // Match CSS type selectors - const char* namespace_schema(const char* src) { - return sequence < - optional < - alternatives < - exactly <'*'>, - css_ip_identifier - > - >, - exactly<'|'>, - negate < - exactly <'='> - > - >(src); - } - - const char* hyphens_and_identifier(const char* src) { - return sequence< zero_plus< exactly< '-' > >, identifier_alnums >(src); - } - const char* hyphens_and_name(const char* src) { - return sequence< zero_plus< exactly< '-' > >, name >(src); - } - const char* universal(const char* src) { - return sequence< optional, exactly<'*'> >(src); - } - // Match CSS id names. - const char* id_name(const char* src) { - return sequence, identifier_alnums >(src); - } - // Match CSS class names. - const char* class_name(const char* src) { - return sequence, identifier >(src); - } - // Attribute name in an attribute selector. - const char* attribute_name(const char* src) { - return alternatives< sequence< optional, identifier>, - identifier >(src); - } - // match placeholder selectors - const char* placeholder(const char* src) { - return sequence, identifier_alnums >(src); - } - // Match CSS numeric constants. - - const char* op(const char* src) { - return class_char(src); - } - const char* sign(const char* src) { - return class_char(src); - } - const char* unsigned_number(const char* src) { - return alternatives, - exactly<'.'>, - one_plus >, - digits>(src); - } - const char* number(const char* src) { - return sequence< - optional, - unsigned_number, - optional< - sequence< - exactly<'e'>, - optional, - unsigned_number - > - > - >(src); - } - const char* coefficient(const char* src) { - return alternatives< sequence< optional, digits >, - sign >(src); - } - const char* binomial(const char* src) { - return sequence < - optional < sign >, - optional < digits >, - exactly <'n'>, - zero_plus < sequence < - optional_css_whitespace, sign, - optional_css_whitespace, digits - > > - >(src); - } - const char* percentage(const char* src) { - return sequence< number, exactly<'%'> >(src); - } - const char* ampersand(const char* src) { - return exactly<'&'>(src); - } - - /* not used anymore - remove? - const char* em(const char* src) { - return sequence< number, exactly >(src); - } */ - const char* dimension(const char* src) { - return sequence(src); - } - const char* hex(const char* src) { - const char* p = sequence< exactly<'#'>, one_plus >(src); - ptrdiff_t len = p - src; - return (len != 4 && len != 7) ? 0 : p; - } - const char* hexa(const char* src) { - const char* p = sequence< exactly<'#'>, one_plus >(src); - ptrdiff_t len = p - src; - return (len != 5 && len != 9) ? 0 : p; - } - const char* hex0(const char* src) { - const char* p = sequence< exactly<'0'>, exactly<'x'>, one_plus >(src); - ptrdiff_t len = p - src; - return (len != 5 && len != 8) ? 0 : p; - } - - /* no longer used - remove? - const char* rgb_prefix(const char* src) { - return word(src); - }*/ - // Match CSS uri specifiers. - - const char* uri_prefix(const char* src) { - return sequence < - exactly < - url_kwd - >, - zero_plus < - sequence < - exactly <'-'>, - one_plus < - alpha - > - > - >, - exactly <'('> - >(src); - } - - // TODO: rename the following two functions - /* no longer used - remove? - const char* uri(const char* src) { - return sequence< exactly, - optional, - quoted_string, - optional, - exactly<')'> >(src); - }*/ - /* no longer used - remove? - const char* url_value(const char* src) { - return sequence< optional< sequence< identifier, exactly<':'> > >, // optional protocol - one_plus< sequence< zero_plus< exactly<'/'> >, filename > >, // one or more folders and/or trailing filename - optional< exactly<'/'> > >(src); - }*/ - /* no longer used - remove? - const char* url_schema(const char* src) { - return sequence< optional< sequence< identifier, exactly<':'> > >, // optional protocol - filename_schema >(src); // optional trailing slash - }*/ - // Match CSS "!important" keyword. - const char* kwd_important(const char* src) { - return sequence< exactly<'!'>, - optional_css_whitespace, - word >(src); - } - // Match CSS "!optional" keyword. - const char* kwd_optional(const char* src) { - return sequence< exactly<'!'>, - optional_css_whitespace, - word >(src); - } - // Match Sass "!default" keyword. - const char* default_flag(const char* src) { - return sequence< exactly<'!'>, - optional_css_whitespace, - word >(src); - } - // Match Sass "!global" keyword. - const char* global_flag(const char* src) { - return sequence< exactly<'!'>, - optional_css_whitespace, - word >(src); - } - // Match CSS pseudo-class/element prefixes. - const char* pseudo_prefix(const char* src) { - return sequence< exactly<':'>, optional< exactly<':'> > >(src); - } - // Match CSS function call openers. - const char* functional_schema(const char* src) { - return sequence < - one_plus < - sequence < - zero_plus < - alternatives < - identifier, - exactly <'-'> - > - >, - one_plus < - sequence < - interpolant, - alternatives < - digits, - identifier, - exactly<'+'>, - exactly<'-'> - > - > - > - > - >, - negate < - exactly <'%'> - >, - lookahead < - exactly <'('> - > - > (src); - } - - const char* re_nothing(const char* src) { - return src; - } - - const char* re_functional(const char* src) { - return sequence< identifier, optional < block_comment >, exactly<'('> >(src); - } - const char* re_pseudo_selector(const char* src) { - return sequence< identifier, optional < block_comment >, exactly<'('> >(src); - } - // Match the CSS negation pseudo-class. - const char* pseudo_not(const char* src) { - return word< pseudo_not_fn_kwd >(src); - } - // Match CSS 'odd' and 'even' keywords for functional pseudo-classes. - const char* even(const char* src) { - return word(src); - } - const char* odd(const char* src) { - return word(src); - } - // Match CSS attribute-matching operators. - const char* exact_match(const char* src) { return exactly<'='>(src); } - const char* class_match(const char* src) { return exactly(src); } - const char* dash_match(const char* src) { return exactly(src); } - const char* prefix_match(const char* src) { return exactly(src); } - const char* suffix_match(const char* src) { return exactly(src); } - const char* substring_match(const char* src) { return exactly(src); } - // Match CSS combinators. - /* not used anymore - remove? - const char* adjacent_to(const char* src) { - return sequence< optional_spaces, exactly<'+'> >(src); - } - const char* precedes(const char* src) { - return sequence< optional_spaces, exactly<'~'> >(src); - } - const char* parent_of(const char* src) { - return sequence< optional_spaces, exactly<'>'> >(src); - } - const char* ancestor_of(const char* src) { - return sequence< spaces, negate< exactly<'{'> > >(src); - }*/ - - // Match SCSS variable names. - const char* variable(const char* src) { - return sequence, identifier>(src); - } - - // parse `calc`, `-a-calc` and `--b-c-calc` - // but do not parse `foocalc` or `foo-calc` - const char* calc_fn_call(const char* src) { - return sequence < - optional < sequence < - hyphens, - one_plus < sequence < - strict_identifier, - hyphens - > > - > >, - exactly < calc_fn_kwd >, - word_boundary - >(src); - } - - // Match Sass boolean keywords. - const char* kwd_true(const char* src) { - return word(src); - } - const char* kwd_false(const char* src) { - return word(src); - } - const char* kwd_only(const char* src) { - return keyword < only_kwd >(src); - } - const char* kwd_and(const char* src) { - return keyword < and_kwd >(src); - } - const char* kwd_or(const char* src) { - return keyword < or_kwd >(src); - } - const char* kwd_not(const char* src) { - return keyword < not_kwd >(src); - } - const char* kwd_eq(const char* src) { - return exactly(src); - } - const char* kwd_neq(const char* src) { - return exactly(src); - } - const char* kwd_gt(const char* src) { - return exactly(src); - } - const char* kwd_gte(const char* src) { - return exactly(src); - } - const char* kwd_lt(const char* src) { - return exactly(src); - } - const char* kwd_lte(const char* src) { - return exactly(src); - } - const char* kwd_using(const char* src) { - return keyword(src); - } - - // match specific IE syntax - const char* ie_progid(const char* src) { - return sequence < - word, - exactly<':'>, - alternatives< identifier_schema, identifier >, - zero_plus< sequence< - exactly<'.'>, - alternatives< identifier_schema, identifier > - > >, - zero_plus < sequence< - exactly<'('>, - optional_css_whitespace, - optional < sequence< - alternatives< variable, identifier_schema, identifier >, - optional_css_whitespace, - exactly<'='>, - optional_css_whitespace, - alternatives< variable, identifier_schema, identifier, quoted_string, number, hex, hexa >, - zero_plus< sequence< - optional_css_whitespace, - exactly<','>, - optional_css_whitespace, - sequence< - alternatives< variable, identifier_schema, identifier >, - optional_css_whitespace, - exactly<'='>, - optional_css_whitespace, - alternatives< variable, identifier_schema, identifier, quoted_string, number, hex, hexa > - > - > > - > >, - optional_css_whitespace, - exactly<')'> - > > - >(src); - } - const char* ie_expression(const char* src) { - return sequence < word, exactly<'('>, skip_over_scopes< exactly<'('>, exactly<')'> > >(src); - } - const char* ie_property(const char* src) { - return alternatives < ie_expression, ie_progid >(src); - } - - // const char* ie_args(const char* src) { - // return sequence< alternatives< ie_keyword_arg, value_schema, quoted_string, interpolant, number, identifier, delimited_by< '(', ')', true> >, - // zero_plus< sequence< optional_css_whitespace, exactly<','>, optional_css_whitespace, alternatives< ie_keyword_arg, value_schema, quoted_string, interpolant, number, identifier, delimited_by<'(', ')', true> > > > >(src); - // } - - const char* ie_keyword_arg_property(const char* src) { - return alternatives < - variable, - identifier_schema, - identifier - >(src); - } - const char* ie_keyword_arg_value(const char* src) { - return alternatives < - variable, - identifier_schema, - identifier, - quoted_string, - number, - hex, - hexa, - sequence < - exactly < '(' >, - skip_over_scopes < - exactly < '(' >, - exactly < ')' > - > - > - >(src); - } - - const char* ie_keyword_arg(const char* src) { - return sequence < - ie_keyword_arg_property, - optional_css_whitespace, - exactly<'='>, - optional_css_whitespace, - ie_keyword_arg_value - >(src); - } - - // Path matching functions. - /* not used anymore - remove? - const char* folder(const char* src) { - return sequence< zero_plus< any_char_except<'/'> >, - exactly<'/'> >(src); - } - const char* folders(const char* src) { - return zero_plus< folder >(src); - }*/ - /* not used anymore - remove? - const char* chunk(const char* src) { - char inside_str = 0; - const char* p = src; - size_t depth = 0; - while (true) { - if (!*p) { - return 0; - } - else if (!inside_str && (*p == '"' || *p == '\'')) { - inside_str = *p; - } - else if (*p == inside_str && *(p-1) != '\\') { - inside_str = 0; - } - else if (*p == '(' && !inside_str) { - ++depth; - } - else if (*p == ')' && !inside_str) { - if (depth == 0) return p; - else --depth; - } - ++p; - } - // unreachable - return 0; - } - */ - - // follow the CSS spec more closely and see if this helps us scan URLs correctly - /* not used anymore - remove? - const char* NL(const char* src) { - return alternatives< exactly<'\n'>, - sequence< exactly<'\r'>, exactly<'\n'> >, - exactly<'\r'>, - exactly<'\f'> >(src); - }*/ - - const char* H(const char* src) { - return Util::ascii_isxdigit(static_cast(*src)) ? src+1 : 0; - } - - const char* W(const char* src) { - return zero_plus< alternatives< - space, - exactly< '\t' >, - exactly< '\r' >, - exactly< '\n' >, - exactly< '\f' > - > >(src); - } - - const char* UUNICODE(const char* src) { - return sequence< exactly<'\\'>, - between, - optional< W > - >(src); - } - - const char* NONASCII(const char* src) { - return nonascii(src); - } - - const char* ESCAPE(const char* src) { - return alternatives< - UUNICODE, - sequence< - exactly<'\\'>, - alternatives< - NONASCII, - escapable_character - > - > - >(src); - } - - const char* list_terminator(const char* src) { - return alternatives < - exactly<';'>, - exactly<'}'>, - exactly<'{'>, - exactly<')'>, - exactly<']'>, - exactly<':'>, - end_of_file, - exactly, - default_flag, - global_flag - >(src); - }; - - const char* space_list_terminator(const char* src) { - return alternatives < - exactly<','>, - list_terminator - >(src); - }; - - - // const char* real_uri_prefix(const char* src) { - // return alternatives< - // exactly< url_kwd >, - // exactly< url_prefix_kwd > - // >(src); - // } - - const char* real_uri(const char* src) { - return sequence< - exactly< url_kwd >, - exactly< '(' >, - W, - real_uri_value, - exactly< ')' > - >(src); - } - - const char* real_uri_suffix(const char* src) { - return sequence< W, exactly< ')' > >(src); - } - - const char* real_uri_value(const char* src) { - return - sequence< - non_greedy< - alternatives< - class_char< real_uri_chars >, - uri_character, - NONASCII, - ESCAPE - >, - alternatives< - real_uri_suffix, - exactly< hash_lbrace > - > - > - > - (src); - } - - const char* static_string(const char* src) { - const char* pos = src; - const char * s = quoted_string(pos); - Token t(pos, s); - const unsigned int p = count_interval< interpolant >(t.begin, t.end); - return (p == 0) ? t.end : 0; - } - - const char* unicode_seq(const char* src) { - return sequence < - alternatives < - exactly< 'U' >, - exactly< 'u' > - >, - exactly< '+' >, - padded_token < - 6, xdigit, - exactly < '?' > - > - >(src); - } - - const char* static_component(const char* src) { - return alternatives< identifier, - static_string, - percentage, - hex, - hexa, - exactly<'|'>, - // exactly<'+'>, - sequence < number, unit_identifier >, - number, - sequence< exactly<'!'>, word > - >(src); - } - - const char* static_property(const char* src) { - return - sequence < - zero_plus< - sequence < - optional_css_comments, - alternatives < - exactly<','>, - exactly<'('>, - exactly<')'>, - kwd_optional, - quoted_string, - interpolant, - identifier, - percentage, - dimension, - variable, - alnum, - sequence < - exactly <'\\'>, - any_char - > - > - > - >, - lookahead < - sequence < - optional_css_comments, - alternatives < - exactly <';'>, - exactly <'}'>, - end_of_file - > - > - > - >(src); - } - - const char* static_value(const char* src) { - return sequence< sequence< - static_component, - zero_plus< identifier > - >, - zero_plus < sequence< - alternatives< - sequence< optional_spaces, alternatives< - exactly < '/' >, - exactly < ',' >, - exactly < ' ' > - >, optional_spaces >, - spaces - >, - static_component - > >, - zero_plus < spaces >, - alternatives< exactly<';'>, exactly<'}'> > - >(src); - } - - extern const char css_variable_url_negates[] = "()[]{}\"'#/"; - const char* css_variable_value(const char* src) { - return sequence< - alternatives< - sequence< - negate< exactly< url_fn_kwd > >, - one_plus< neg_class_char< css_variable_url_negates > > - >, - sequence< exactly<'#'>, negate< exactly<'{'> > >, - sequence< exactly<'/'>, negate< exactly<'*'> > >, - static_string, - real_uri, - block_comment - > - >(src); - } - - extern const char css_variable_url_top_level_negates[] = "()[]{}\"'#/;"; - const char* css_variable_top_level_value(const char* src) { - return sequence< - alternatives< - sequence< - negate< exactly< url_fn_kwd > >, - one_plus< neg_class_char< css_variable_url_top_level_negates > > - >, - sequence< exactly<'#'>, negate< exactly<'{'> > >, - sequence< exactly<'/'>, negate< exactly<'*'> > >, - static_string, - real_uri, - block_comment - > - >(src); - } - - const char* parenthese_scope(const char* src) { - return sequence < - exactly < '(' >, - skip_over_scopes < - exactly < '(' >, - exactly < ')' > - > - >(src); - } - - const char* re_selector_list(const char* src) { - return alternatives < - // partial bem selector - sequence < - ampersand, - one_plus < - exactly < '-' > - >, - word_boundary, - optional_spaces - >, - // main selector matching - one_plus < - alternatives < - // consume whitespace and comments - spaces, block_comment, line_comment, - // match `/deep/` selector (pass-trough) - // there is no functionality for it yet - schema_reference_combinator, - // match selector ops /[*&%,\[\]]/ - class_char < selector_lookahead_ops >, - // match selector combinators /[>+~]/ - class_char < selector_combinator_ops >, - // match pseudo selectors - sequence < - exactly <'('>, - optional_spaces, - optional , - optional_spaces, - exactly <')'> - >, - // match attribute compare operators - alternatives < - exact_match, class_match, dash_match, - prefix_match, suffix_match, substring_match - >, - // main selector match - sequence < - // allow namespace prefix - optional < namespace_schema >, - // modifiers prefixes - alternatives < - sequence < - exactly <'#'>, - // not for interpolation - negate < exactly <'{'> > - >, - // class match - exactly <'.'>, - // single or double colon - sequence < - optional < pseudo_prefix >, - // fix libsass issue 2376 - negate < uri_prefix > - > - >, - // accept hypens in token - one_plus < sequence < - // can start with hyphens - zero_plus < - sequence < - exactly <'-'>, - optional_spaces - > - >, - // now the main token - alternatives < - kwd_optional, - exactly <'*'>, - quoted_string, - interpolant, - identifier, - variable, - percentage, - binomial, - dimension, - alnum - > - > >, - // can also end with hyphens - zero_plus < exactly<'-'> > - > - > - > - >(src); - } - - const char* type_selector(const char* src) { - return sequence< optional, identifier>(src); - } - const char* re_type_selector(const char* src) { - return alternatives< type_selector, universal, dimension, percentage, number, identifier_alnums >(src); - } - const char* re_static_expression(const char* src) { - return sequence< number, optional_spaces, exactly<'/'>, optional_spaces, number >(src); - } - - // lexer special_fn: these functions cannot be overloaded - // (/((-[\w-]+-)?(calc|element)|expression|progid:[a-z\.]*)\(/i) - const char* re_special_fun(const char* src) { - - // match this first as we test prefix hyphens - if (const char* calc = calc_fn_call(src)) { - return calc; - } - - return sequence < - optional < - sequence < - exactly <'-'>, - one_plus < - alternatives < - alpha, - exactly <'+'>, - exactly <'-'> - > - > - > - >, - alternatives < - word < expression_kwd >, - sequence < - sequence < - exactly < progid_kwd >, - exactly <':'> - >, - zero_plus < - alternatives < - char_range <'a', 'z'>, - exactly <'.'> - > - > - > - > - >(src); - } - - } -} diff --git a/src/prelexer.hpp b/src/prelexer.hpp deleted file mode 100644 index ba49fc93cd..0000000000 --- a/src/prelexer.hpp +++ /dev/null @@ -1,484 +0,0 @@ -#ifndef SASS_PRELEXER_H -#define SASS_PRELEXER_H - -#include -#include "lexer.hpp" - -namespace Sass { - // using namespace Lexer; - namespace Prelexer { - - //#################################### - // KEYWORD "REGEX" MATCHERS - //#################################### - - // Match Sass boolean keywords. - const char* kwd_true(const char* src); - const char* kwd_false(const char* src); - const char* kwd_only(const char* src); - const char* kwd_and(const char* src); - const char* kwd_or(const char* src); - const char* kwd_not(const char* src); - const char* kwd_eq(const char* src); - const char* kwd_neq(const char* src); - const char* kwd_gt(const char* src); - const char* kwd_gte(const char* src); - const char* kwd_lt(const char* src); - const char* kwd_lte(const char* src); - const char* kwd_using(const char* src); - - // Match standard control chars - const char* kwd_at(const char* src); - const char* kwd_dot(const char* src); - const char* kwd_comma(const char* src); - const char* kwd_colon(const char* src); - const char* kwd_slash(const char* src); - const char* kwd_star(const char* src); - const char* kwd_plus(const char* src); - const char* kwd_minus(const char* src); - - //#################################### - // SPECIAL "REGEX" CONSTRUCTS - //#################################### - - // Match a sequence of characters delimited by the supplied chars. - template - const char* delimited_by(const char* src) { - src = exactly(src); - if (!src) return 0; - const char* stop; - while (true) { - if (!*src) return 0; - stop = exactly(src); - if (stop && (!esc || *(src - 1) != '\\')) return stop; - src = stop ? stop : src + 1; - } - } - - // skip to delimiter (mx) inside given range - // this will savely skip over all quoted strings - // recursive skip stuff delimited by start/stop - // first start/opener must be consumed already! - template - const char* skip_over_scopes(const char* src, const char* end) { - - size_t level = 0; - bool in_squote = false; - bool in_dquote = false; - bool in_backslash_escape = false; - - while ((end == nullptr || src < end) && *src != '\0') { - // has escaped sequence? - if (in_backslash_escape) { - in_backslash_escape = false; - } - else if (*src == '\\') { - in_backslash_escape = true; - } - else if (*src == '"') { - in_dquote = ! in_dquote; - } - else if (*src == '\'') { - in_squote = ! in_squote; - } - else if (in_dquote || in_squote) { - // take everything literally - } - - // find another opener inside? - else if (const char* pos = start(src)) { - ++ level; // increase counter - src = pos - 1; // advance position - } - - // look for the closer (maybe final, maybe not) - else if (const char* final = stop(src)) { - // only close one level? - if (level > 0) -- level; - // return position at end of stop - // delimiter may be multiple chars - else return final; - // advance position - src = final - 1; - } - - // next - ++ src; - } - - return 0; - } - - // skip to a skip delimited by parentheses - // uses smart `skip_over_scopes` internally - const char* parenthese_scope(const char* src); - - // skip to delimiter (mx) inside given range - // this will savely skip over all quoted strings - // recursive skip stuff delimited by start/stop - // first start/opener must be consumed already! - template - const char* skip_over_scopes(const char* src) { - return skip_over_scopes(src, nullptr); - } - - // Match a sequence of characters delimited by the supplied chars. - template - const char* recursive_scopes(const char* src) { - // parse opener - src = start(src); - // abort if not found - if (!src) return 0; - // parse the rest until final closer - return skip_over_scopes(src); - } - - // Match a sequence of characters delimited by the supplied strings. - template - const char* delimited_by(const char* src) { - src = exactly(src); - if (!src) return 0; - const char* stop; - while (true) { - if (!*src) return 0; - stop = exactly(src); - if (stop && (!esc || *(src - 1) != '\\')) return stop; - src = stop ? stop : src + 1; - } - } - - // Tries to match a certain number of times (between the supplied interval). - template - const char* between(const char* src) { - for (size_t i = 0; i < lo; ++i) { - src = mx(src); - if (!src) return 0; - } - for (size_t i = lo; i <= hi; ++i) { - const char* new_src = mx(src); - if (!new_src) return src; - src = new_src; - } - return src; - } - - // equivalent of STRING_REGULAR_EXPRESSIONS - const char* re_string_double_open(const char* src); - const char* re_string_double_close(const char* src); - const char* re_string_single_open(const char* src); - const char* re_string_single_close(const char* src); - const char* re_string_uri_open(const char* src); - const char* re_string_uri_close(const char* src); - - // Match a line comment. - const char* line_comment(const char* src); - - // Match a block comment. - const char* block_comment(const char* src); - // Match either. - const char* comment(const char* src); - // Match double- and single-quoted strings. - const char* double_quoted_string(const char* src); - const char* single_quoted_string(const char* src); - const char* quoted_string(const char* src); - // Match interpolants. - const char* interpolant(const char* src); - // Match number prefix ([\+\-]+) - const char* number_prefix(const char* src); - - // Match zero plus white-space or line_comments - const char* optional_css_whitespace(const char* src); - const char* css_whitespace(const char* src); - // Match optional_css_whitespace plus block_comments - const char* optional_css_comments(const char* src); - const char* css_comments(const char* src); - - // Match one backslash escaped char - const char* escape_seq(const char* src); - - // Match CSS css variables. - const char* custom_property_name(const char* src); - // Match a CSS identifier. - const char* identifier(const char* src); - const char* identifier_alpha(const char* src); - const char* identifier_alnum(const char* src); - const char* strict_identifier(const char* src); - const char* strict_identifier_alpha(const char* src); - const char* strict_identifier_alnum(const char* src); - // Match a CSS unit identifier. - const char* one_unit(const char* src); - const char* multiple_units(const char* src); - const char* unit_identifier(const char* src); - // const char* strict_identifier_alnums(const char* src); - // Match reference selector. - const char* re_reference_combinator(const char* src); - const char* static_reference_combinator(const char* src); - const char* schema_reference_combinator(const char* src); - - // Match interpolant schemas - const char* identifier_schema(const char* src); - const char* value_schema(const char* src); - const char* sass_value(const char* src); - // const char* filename(const char* src); - // const char* filename_schema(const char* src); - // const char* url_schema(const char* src); - // const char* url_value(const char* src); - const char* vendor_prefix(const char* src); - - const char* re_special_directive(const char* src); - const char* re_prefixed_directive(const char* src); - const char* re_almost_any_value_token(const char* src); - - // Match CSS '@' keywords. - const char* at_keyword(const char* src); - const char* kwd_import(const char* src); - const char* kwd_at_root(const char* src); - const char* kwd_with_directive(const char* src); - const char* kwd_without_directive(const char* src); - const char* kwd_media(const char* src); - const char* kwd_supports_directive(const char* src); - // const char* keyframes(const char* src); - // const char* keyf(const char* src); - const char* kwd_mixin(const char* src); - const char* kwd_function(const char* src); - const char* kwd_return_directive(const char* src); - const char* kwd_include_directive(const char* src); - const char* kwd_content_directive(const char* src); - const char* kwd_charset_directive(const char* src); - const char* kwd_extend(const char* src); - - const char* unicode_seq(const char* src); - - const char* kwd_if_directive(const char* src); - const char* kwd_else_directive(const char* src); - const char* elseif_directive(const char* src); - - const char* kwd_for_directive(const char* src); - const char* kwd_from(const char* src); - const char* kwd_to(const char* src); - const char* kwd_through(const char* src); - - const char* kwd_each_directive(const char* src); - const char* kwd_in(const char* src); - - const char* kwd_while_directive(const char* src); - - const char* re_nothing(const char* src); - - const char* re_special_fun(const char* src); - - const char* kwd_warn(const char* src); - const char* kwd_err(const char* src); - const char* kwd_dbg(const char* src); - - const char* kwd_null(const char* src); - - const char* re_selector_list(const char* src); - const char* re_type_selector(const char* src); - const char* re_static_expression(const char* src); - - // identifier that can start with hyphens - const char* css_identifier(const char* src); - const char* css_ip_identifier(const char* src); - - // Match CSS type selectors - const char* namespace_schema(const char* src); - const char* namespace_prefix(const char* src); - const char* type_selector(const char* src); - const char* hyphens_and_identifier(const char* src); - const char* hyphens_and_name(const char* src); - const char* universal(const char* src); - // Match CSS id names. - const char* id_name(const char* src); - // Match CSS class names. - const char* class_name(const char* src); - // Attribute name in an attribute selector - const char* attribute_name(const char* src); - // Match placeholder selectors. - const char* placeholder(const char* src); - // Match CSS numeric constants. - const char* op(const char* src); - const char* sign(const char* src); - const char* unsigned_number(const char* src); - const char* number(const char* src); - const char* coefficient(const char* src); - const char* binomial(const char* src); - const char* percentage(const char* src); - const char* ampersand(const char* src); - const char* dimension(const char* src); - const char* hex(const char* src); - const char* hexa(const char* src); - const char* hex0(const char* src); - // const char* rgb_prefix(const char* src); - // Match CSS uri specifiers. - const char* uri_prefix(const char* src); - // Match CSS "!important" keyword. - const char* kwd_important(const char* src); - // Match CSS "!optional" keyword. - const char* kwd_optional(const char* src); - // Match Sass "!default" keyword. - const char* default_flag(const char* src); - const char* global_flag(const char* src); - // Match CSS pseudo-class/element prefixes - const char* pseudo_prefix(const char* src); - // Match CSS function call openers. - const char* re_functional(const char* src); - const char* re_pseudo_selector(const char* src); - const char* functional_schema(const char* src); - const char* pseudo_not(const char* src); - // Match CSS 'odd' and 'even' keywords for functional pseudo-classes. - const char* even(const char* src); - const char* odd(const char* src); - // Match CSS attribute-matching operators. - const char* exact_match(const char* src); - const char* class_match(const char* src); - const char* dash_match(const char* src); - const char* prefix_match(const char* src); - const char* suffix_match(const char* src); - const char* substring_match(const char* src); - // Match CSS combinators. - // const char* adjacent_to(const char* src); - // const char* precedes(const char* src); - // const char* parent_of(const char* src); - // const char* ancestor_of(const char* src); - - // Match SCSS variable names. - const char* variable(const char* src); - const char* calc_fn_call(const char* src); - - // IE stuff - const char* ie_progid(const char* src); - const char* ie_expression(const char* src); - const char* ie_property(const char* src); - const char* ie_keyword_arg(const char* src); - const char* ie_keyword_arg_value(const char* src); - const char* ie_keyword_arg_property(const char* src); - - // characters that terminate parsing of a list - const char* list_terminator(const char* src); - const char* space_list_terminator(const char* src); - - // match url() - const char* H(const char* src); - const char* W(const char* src); - // `UNICODE` makes VS sad - const char* UUNICODE(const char* src); - const char* NONASCII(const char* src); - const char* ESCAPE(const char* src); - const char* real_uri(const char* src); - const char* real_uri_suffix(const char* src); - // const char* real_uri_prefix(const char* src); - const char* real_uri_value(const char* src); - - // Path matching functions. - // const char* folder(const char* src); - // const char* folders(const char* src); - - - const char* static_string(const char* src); - const char* static_component(const char* src); - const char* static_property(const char* src); - const char* static_value(const char* src); - - const char* css_variable_value(const char* src); - const char* css_variable_top_level_value(const char* src); - - // Utility functions for finding and counting characters in a string. - template - const char* find_first(const char* src) { - while (*src && *src != c) ++src; - return *src ? src : 0; - } - template - const char* find_first(const char* src) { - while (*src && !mx(src)) ++src; - return *src ? src : 0; - } - template - const char* find_first_in_interval(const char* beg, const char* end) { - bool esc = false; - while ((beg < end) && *beg) { - if (esc) esc = false; - else if (*beg == '\\') esc = true; - else if (mx(beg)) return beg; - ++beg; - } - return 0; - } - template - const char* find_first_in_interval(const char* beg, const char* end) { - bool esc = false; - while ((beg < end) && *beg) { - if (esc) esc = false; - else if (*beg == '\\') esc = true; - else if (const char* pos = skip(beg)) beg = pos; - else if (mx(beg)) return beg; - ++beg; - } - return 0; - } - template - unsigned int count_interval(const char* beg, const char* end) { - unsigned int counter = 0; - bool esc = false; - while (beg < end && *beg) { - const char* p; - if (esc) { - esc = false; - ++beg; - } else if (*beg == '\\') { - esc = true; - ++beg; - } else if ((p = mx(beg))) { - ++counter; - beg = p; - } - else { - ++beg; - } - } - return counter; - } - - template - const char* padded_token(const char* src) - { - size_t got = 0; - const char* pos = src; - while (got < size) { - if (!mx(pos)) break; - ++ pos; ++ got; - } - while (got < size) { - if (!pad(pos)) break; - ++ pos; ++ got; - } - return got ? pos : 0; - } - - template - const char* minmax_range(const char* src) - { - size_t got = 0; - const char* pos = src; - while (got < max) { - if (!mx(pos)) break; - ++ pos; ++ got; - } - if (got < min) return 0; - if (got > max) return 0; - return pos; - } - - template - const char* char_range(const char* src) - { - if (*src < min) return 0; - if (*src > max) return 0; - return src + 1; - } - - } -} - -#endif diff --git a/src/preloader.cpp b/src/preloader.cpp new file mode 100644 index 0000000000..151eed87ce --- /dev/null +++ b/src/preloader.cpp @@ -0,0 +1,222 @@ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#include "preloader.hpp" + +#include "eval.hpp" +#include "compiler.hpp" +#include "exceptions.hpp" +#include "environment.hpp" +#include "ast_imports.hpp" + +namespace Sass { + + Preloader::Preloader(Eval& eval, Root* root) : + eval(eval), + root(root), + compiler(eval.compiler), + modctx(eval.modctx42), + wconfig(eval.wconfig), + idxs(root->idxs) + {} + + // During the whole parsing we should keep a big map of + // Variable name to EnvRef vector with all alternatives + + void Preloader::process() + { + acceptRoot(root); + } + + void Preloader::acceptRoot(Root* sheet) + { + if (sheet && !sheet->empty()) { + RAII_MODULE(modules, root); + RAII_PTR(Root, modctx, sheet); + RAII_PTR(EnvRefs, idxs, sheet->idxs); + ImportStackFrame isf(compiler, sheet->import); + compiler.varRoot.stack.push_back(sheet->idxs); + for (auto& it : sheet->elements()) it->accept(this); + compiler.varRoot.stack.pop_back(); + } + } + + void Preloader::visitParentStatement(ParentStatement* rule) + { + if (rule->empty()) return; + RAII_PTR(EnvRefs, idxs, rule->idxs); + compiler.varRoot.stack.push_back(rule->idxs); + for (auto& it : rule->elements()) it->accept(this); + compiler.varRoot.stack.pop_back(); + } + + void Preloader::visitUseRule(UseRule* rule) + { + callStackFrame frame(compiler, { + rule->pstate(), Strings::useRule }); + acceptRoot(eval.loadModRule(rule)); + eval.exposeUseRule(rule); + } + + void Preloader::visitForwardRule(ForwardRule* rule) + { + callStackFrame frame(compiler, { + rule->pstate(), Strings::forwardRule }); + acceptRoot(eval.loadModRule(rule)); + eval.exposeFwdRule(rule); + } + + void Preloader::visitIncludeImport(IncludeImport* rule) + { + callStackFrame frame(compiler, { + rule->pstate(), Strings::importRule }); + // We could demux glob-stars here + acceptRoot(eval.resolveIncludeImport(rule)); + eval.exposeImpRule(rule); + } + + void Preloader::visitAssignRule(AssignRule* rule) + { + if (!rule->ns().empty()) return; + } + + void Preloader::visitFunctionRule(FunctionRule* rule) + { + RAII_PTR(EnvRefs, idxs, rule->idxs); + compiler.varRoot.stack.push_back(rule->idxs); + for (auto& it : rule->elements()) it->accept(this); + compiler.varRoot.stack.pop_back(); + } + + void Preloader::visitMixinRule(MixinRule* rule) + { + RAII_PTR(EnvRefs, idxs, rule->idxs); + compiler.varRoot.stack.push_back(rule->idxs); + for (auto& it : rule->elements()) it->accept(this); + compiler.varRoot.stack.pop_back(); + } + + void Preloader::visitImportRule(ImportRule* rule) + { + for (const ImportBaseObj& import : rule->elements()) { + if (IncludeImport* include = import->isaIncludeImport()) { + visitIncludeImport(include); + } + } + } + + void Preloader::visitAtRootRule(AtRootRule* rule) + { + visitParentStatement(rule); + } + + void Preloader::visitAtRule(AtRule* rule) + { + visitParentStatement(rule); + } + + void Preloader::visitContentBlock(ContentBlock* rule) + { + visitParentStatement(rule); + } + + void Preloader::visitContentRule(ContentRule* rule) + { + } + + void Preloader::visitDebugRule(DebugRule* rule) + { + } + + void Preloader::visitDeclaration(Declaration* rule) + { + visitParentStatement(rule); + } + + void Preloader::visitErrorRule(ErrorRule* rule) + { + } + + void Preloader::visitExtendRule(ExtendRule* rule) + { + } + + void Preloader::visitIfRule(IfRule* rule) + { + visitParentStatement(rule); + if (rule->alternative() == nullptr) return; + visitIfRule(rule->alternative()); + } + + + void Preloader::visitIncludeRule(IncludeRule* rule) + { + if (ContentBlock* content = rule->content()) { + RAII_PTR(EnvRefs, idxs, content->idxs); + compiler.varRoot.stack.push_back(content->idxs); + for (auto& it : content->elements()) it->accept(this); + compiler.varRoot.stack.pop_back(); + } + } + + void Preloader::visitLoudComment(LoudComment* rule) + { + } + + void Preloader::visitMediaRule(MediaRule* rule) + { + visitParentStatement(rule); + } + + void Preloader::visitEachRule(EachRule* rule) + { + RAII_PTR(EnvRefs, idxs, rule->idxs); + auto& vars(rule->variables()); + for (size_t i = 0; i < vars.size(); i += 1) { + idxs->varIdxs.insert({ vars[i], (uint32_t)i }); + } + compiler.varRoot.stack.push_back(rule->idxs); + for (auto& it : rule->elements()) it->accept(this); + compiler.varRoot.stack.pop_back(); + } + + void Preloader::visitForRule(ForRule* rule) + { + RAII_PTR(EnvRefs, idxs, rule->idxs); + idxs->varIdxs.insert({ rule->varname(), 0 }); + compiler.varRoot.stack.push_back(rule->idxs); + for (auto& it : rule->elements()) it->accept(this); + compiler.varRoot.stack.pop_back(); + } + + void Preloader::visitReturnRule(ReturnRule* rule) + { + } + + void Preloader::visitSilentComment(SilentComment* rule) + { + } + + void Preloader::visitStyleRule(StyleRule* rule) + { + visitParentStatement(rule); + } + + void Preloader::visitSupportsRule(SupportsRule* rule) + { + visitParentStatement(rule); + } + + void Preloader::visitWarnRule(WarnRule* rule) + { + } + + void Preloader::visitWhileRule(WhileRule* rule) + { + visitParentStatement(rule); + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + +}; diff --git a/src/preloader.hpp b/src/preloader.hpp new file mode 100644 index 0000000000..a2989b993f --- /dev/null +++ b/src/preloader.hpp @@ -0,0 +1,86 @@ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#ifndef SASS_PRELOADER_HPP +#define SASS_PRELOADER_HPP + +// sass.hpp must go before all system headers +// to get the __EXTENSIONS__ fix on Solaris. +#include "capi_sass.hpp" + +#include "environment_key.hpp" +#include "environment_cnt.hpp" +#include "visitor_statement.hpp" + +namespace Sass { + + /*#####################################################################*/ + /*#####################################################################*/ + + class Preloader : + public StatementVisitor + { + + public: + + Eval& eval; + Root* root; + Compiler& compiler; + + // Alias into context + Root*& modctx; + + sass::vector modules; + + // Alias into context + WithConfig*& wconfig; + + // Current lexical scope + EnvRefs* idxs; + + void visitParentStatement(ParentStatement* rule); + void visitIncludeImport(IncludeImport* include); + + public: + + Preloader( + Eval& eval, + Root* root); + + void process(); + + void acceptRoot(Root* root); + + + void visitAtRootRule(AtRootRule* rule) override final; + void visitAtRule(AtRule* rule) override final; + void visitContentBlock(ContentBlock* rule) override final; + void visitContentRule(ContentRule* rule) override final; + void visitDebugRule(DebugRule* rule) override final; + void visitDeclaration(Declaration* rule) override final; + void visitEachRule(EachRule* rule) override final; + void visitErrorRule(ErrorRule* rule) override final; + void visitExtendRule(ExtendRule* rule) override final; + void visitForRule(ForRule* rule) override final; + void visitForwardRule(ForwardRule* rule) override final; + void visitFunctionRule(FunctionRule* rule) override final; + void visitIfRule(IfRule* rule) override final; + void visitImportRule(ImportRule* rule) override final; + void visitIncludeRule(IncludeRule* rule) override final; + void visitLoudComment(LoudComment* rule) override final; + void visitMediaRule(MediaRule* rule) override final; + void visitMixinRule(MixinRule* rule) override final; + void visitReturnRule(ReturnRule* rule) override final; + void visitSilentComment(SilentComment* rule) override final; + void visitStyleRule(StyleRule* rule) override final; + void visitSupportsRule(SupportsRule* rule) override final; + void visitUseRule(UseRule* rule) override final; + void visitAssignRule(AssignRule* rule) override final; + void visitWarnRule(WarnRule* rule) override final; + void visitWhileRule(WhileRule* rule) override final; + + }; + +} + +#endif diff --git a/src/randomize.cpp b/src/randomize.cpp new file mode 100644 index 0000000000..e5aac23e57 --- /dev/null +++ b/src/randomize.cpp @@ -0,0 +1,130 @@ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#include "capi_sass.hpp" + +#include +#ifdef USE_WIN_CRYPT +#include +#include +#else +#include +#include +#endif + +namespace Sass { + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // Read a truly random seed + // This is probably expensive + uint32_t readHashSeed() + { + // Our hash seed + uint32_t seed = 0; + // Load optional fixed seed from environment + // Mainly used to pass the seed to plugins + if (const char* envseed = GET_ENV("SASS_HASH_SEED")) { + seed = atol(envseed); + } + else { + #ifdef SassStaticHashSeed + seed = SassStaticHashSeed; + #elsifdef USE_WIN_CRYPT + // Get seed via MS API + HCRYPTPROV hp = 0; + CryptAcquireContext(&hp, 0, 0, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT); + CryptGenRandom(hp, sizeof(seed), (BYTE*)&seed); + CryptReleaseContext(hp, 0); + #else + // Try to get random number from system + try { + // Get seed via C++11 API + std::random_device rd; + seed = rd(); + } + // On certain system this can throw since either + // underlying hardware or software can be buggy. + // https://github.com/sass/libsass/issues/3151 + catch (std::exception&) { + } + // Don't trust anyone to be random, so we + // add a little entropy of our own. + seed ^= std::time(NULL) ^ std::clock() ^ + std::hash() + (std::this_thread::get_id()); + // Return entropy + #endif + } + // Use some sensible default + if (seed == 0) { + // Fibonacci/Golden Ratio Hashing + seed = 0x9e3779b9; + } + // Return the seed + return seed; + } + // EO readHashSeed + + // Just a static wrapped around random device + // Creates one true random number to seed us + uint32_t getHashSeed(uint32_t* preset) + { + #ifdef SassStaticHashSeed + return SassStaticHashSeed + #else + // This should be thread safe + static uint32_t seed = preset + ? *preset : readHashSeed(); + if (preset) seed = *preset; + return seed; // thread static + #endif + } + // EO getHashSeed + + // Random number generator only needed in eval phase + // This makes it safe to reset the hash seed before + std::mt19937& getRng() + { + // Lets hope this is indeed thread safe (seed once) + static std::mt19937 rng(getHashSeed()); + return rng; // static twister per thread + } + // EO getRng + + // Create a random `int` between [low] and [high] + int getRandomInt(int low, int high) + { + static std::uniform_int_distribution urd; + return urd(getRng(), decltype(urd)::param_type{ low, high }); + } + // EO getRandomInt + + // Create a random `float` between [low] and [high] + float getRandomFloat(float low, float high) + { + static std::uniform_real_distribution urd; + return urd(getRng(), decltype(urd)::param_type{ low, high }); + } + // EO getRandomFloat + + // Create a random `double` between [low] and [high] + double getRandomDouble(double low, double high) + { + static std::uniform_real_distribution urd; + return urd(getRng(), decltype(urd)::param_type{ low, high }); + } + // EO getRandomDouble + + // Get full 32bit random data + uint32_t getRandomUint32() + { + return getRng()(); + } + // EO getRandomUint32 + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + +} diff --git a/src/randomize.hpp b/src/randomize.hpp new file mode 100644 index 0000000000..6910c339bf --- /dev/null +++ b/src/randomize.hpp @@ -0,0 +1,44 @@ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#ifndef SASS_RANDOMIZE_HPP +#define SASS_RANDOMIZE_HPP + +#include + +// Macros for env variables +#ifdef _WIN32 +#define GET_ENV(key) getenv(key) +#define SET_ENV(key, val) _putenv_s(key, val) +#else +#define GET_ENV(key) getenv(key) +#define SET_ENV(key, val) setenv(key, val, 0) +#endif + +namespace Sass { + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // Just a static wrapped around random device + // Creates one true random number to seed us + uint32_t getHashSeed(uint32_t* preset = nullptr); + + // Create a random `int` between [low] and [high] + int getRandomInt(int low = 0.0, int high = 1.0); + + // Create a random `float` between [low] and [high] + float getRandomFloat(float low = 0.0, float high = 1.0); + + // Create a random `double` between [low] and [high] + double getRandomDouble(double low = 0.0, double high = 1.0); + + // Get full 32bit random data + uint32_t getRandomUint32(); + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + +}; + +#endif diff --git a/src/remove_placeholders.cpp b/src/remove_placeholders.cpp index bc99c57e4a..7a3555fa83 100644 --- a/src/remove_placeholders.cpp +++ b/src/remove_placeholders.cpp @@ -1,86 +1,81 @@ -// sass.hpp must go before all system headers to get the -// __EXTENSIONS__ fix on Solaris. -#include "sass.hpp" -#include "ast.hpp" - +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ #include "remove_placeholders.hpp" -namespace Sass { +#include "ast_selectors.hpp" - Remove_Placeholders::Remove_Placeholders() - { } +namespace Sass { - void Remove_Placeholders::operator()(Block* b) { - for (size_t i = 0, L = b->length(); i < L; ++i) { - if (b->get(i)) b->get(i)->perform(this); - } + bool isInvisibleCss(CssNode* stmt) { + return stmt->isInvisibleCss(); } - void Remove_Placeholders::remove_placeholders(SimpleSelector* simple) + void RemovePlaceholders::remove_placeholders(SimpleSelector* simple) { - if (PseudoSelector * pseudo = simple->getPseudoSelector()) { + if (PseudoSelector * pseudo = simple->isaPseudoSelector()) { if (pseudo->selector()) remove_placeholders(pseudo->selector()); } } - void Remove_Placeholders::remove_placeholders(CompoundSelector* compound) + void RemovePlaceholders::remove_placeholders(CompoundSelector* compound) { - for (size_t i = 0, L = compound->length(); i < L; ++i) { + for (size_t i = 0, L = compound->size(); i < L; ++i) { if (compound->get(i)) remove_placeholders(compound->get(i)); } - listEraseItemIf(compound->elements(), listIsEmpty); + // listEraseItemIf(compound->elements(), listIsInvisible); + // listEraseItemIf(compound->elements(), listIsEmpty); } - void Remove_Placeholders::remove_placeholders(ComplexSelector* complex) + void RemovePlaceholders::remove_placeholders(ComplexSelector* complex) { - if (complex->has_placeholder()) { - complex->clear(); // remove all - } - else { - for (size_t i = 0, L = complex->length(); i < L; ++i) { - if (CompoundSelector * compound = complex->get(i)->getCompound()) { - if (compound) remove_placeholders(compound); - } + for (const CplxSelComponentObj& component : complex->elements()) { + if (component->hasPlaceholder()) { + complex->clear(); // remove all + return; + } + if (CompoundSelector* compound = component->selector()) { + if (compound) remove_placeholders(compound); } - listEraseItemIf(complex->elements(), listIsEmpty); } + complex->eraseIf(listIsEmpty); + // ToDo: describe what this means + //if (complex->hasInvisible()) { + // complex->clear(); // remove all + //} } - SelectorList* Remove_Placeholders::remove_placeholders(SelectorList* sl) + void RemovePlaceholders::remove_placeholders(SelectorList* sl) { - for (size_t i = 0, L = sl->length(); i < L; ++i) { - if (sl->get(i)) remove_placeholders(sl->get(i)); + for(const ComplexSelectorObj& complex : sl->elements()) { + if (complex) remove_placeholders(complex); } - listEraseItemIf(sl->elements(), listIsEmpty); - return sl; + sl->eraseIf(listIsEmpty); } - void Remove_Placeholders::operator()(CssMediaRule* rule) + void RemovePlaceholders::visitCssRoot(CssRoot* b) { - if (rule->block()) operator()(rule->block()); + // Clean up all our children + acceptCssParentNode(b); + // Remove all invisible items + //b->eraseIf(isInvisibleCss); } - void Remove_Placeholders::operator()(StyleRule* r) + void RemovePlaceholders::visitCssStyleRule(CssStyleRule* rule) { - if (SelectorListObj sl = r->selector()) { - // Set the new placeholder selector list - r->selector((remove_placeholders(sl))); - } - // Iterate into child blocks - Block_Obj b = r->block(); - for (size_t i = 0, L = b->length(); i < L; ++i) { - if (b->get(i)) { b->get(i)->perform(this); } - } - } + // Clean up all our children + acceptCssParentNode(rule); - void Remove_Placeholders::operator()(SupportsRule* m) - { - if (m->block()) operator()(m->block()); + if (SelectorList* sl = rule->selector()) { + remove_placeholders(sl); + } } - void Remove_Placeholders::operator()(AtRule* a) + void RemovePlaceholders::acceptCssParentNode(CssParentNode* m) { - if (a->block()) a->block()->perform(this); + for (const CssNodeObj& node : m->elements()) { + node->accept(this); + } } } diff --git a/src/remove_placeholders.hpp b/src/remove_placeholders.hpp index ae0af37f11..ec9b737f32 100644 --- a/src/remove_placeholders.hpp +++ b/src/remove_placeholders.hpp @@ -1,34 +1,49 @@ -#ifndef SASS_REMOVE_PLACEHOLDERS_H -#define SASS_REMOVE_PLACEHOLDERS_H +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#ifndef SASS_REMOVE_PLACEHOLDERS_HPP +#define SASS_REMOVE_PLACEHOLDERS_HPP +// sass.hpp must go before all system headers +// to get the __EXTENSIONS__ fix on Solaris. +#include "capi_sass.hpp" + +#include "ast_css.hpp" #include "ast_fwd_decl.hpp" -#include "operation.hpp" +#include "visitor_css.hpp" namespace Sass { - class Remove_Placeholders : public Operation_CRTP { + class RemovePlaceholders : + public CssVisitor { - public: + private: - SelectorList* remove_placeholders(SelectorList*); - void remove_placeholders(SimpleSelector* simple); - void remove_placeholders(CompoundSelector* complex); - void remove_placeholders(ComplexSelector* complex); + // Helper to traverse the tree recursively + void acceptCssParentNode(CssParentNode*); + // The main functions to do the cleanup + void remove_placeholders(SelectorList*); + void remove_placeholders(SimpleSelector*); + void remove_placeholders(CompoundSelector*); + void remove_placeholders(ComplexSelector*); public: - Remove_Placeholders(); - ~Remove_Placeholders() { } - - void operator()(Block*); - void operator()(StyleRule*); - void operator()(CssMediaRule*); - void operator()(SupportsRule*); - void operator()(AtRule*); - - // ignore missed types - template - void fallback(U x) {} + + // Do not implement anything for these visitors + void visitCssComment(CssComment* css) override final {}; + void visitCssDeclaration(CssDeclaration* css) override final {}; + void visitCssImport(CssImport* css) override final {}; + + // Move further down into children to remove recursively + void visitCssAtRule(CssAtRule* css) override final { acceptCssParentNode(css); }; + void visitCssKeyframeBlock(CssKeyframeBlock* css) override final { acceptCssParentNode(css); }; + void visitCssMediaRule(CssMediaRule* css) override final { acceptCssParentNode(css); }; + void visitCssSupportsRule(CssSupportsRule* css) override final { acceptCssParentNode(css); }; + + // Cleaning only makes sense on those nodes + void visitCssRoot(CssRoot*) override final; + void visitCssStyleRule(CssStyleRule*) override final; }; diff --git a/src/sass.cpp b/src/sass.cpp deleted file mode 100644 index 35383ebf08..0000000000 --- a/src/sass.cpp +++ /dev/null @@ -1,156 +0,0 @@ -// sass.hpp must go before all system headers to get the -// __EXTENSIONS__ fix on Solaris. -#include "sass.hpp" - -#include -#include -#include -#include - -#include "sass.h" -#include "file.hpp" -#include "util.hpp" -#include "context.hpp" -#include "sass_context.hpp" -#include "sass_functions.hpp" - -namespace Sass { - - // helper to convert string list to vector - sass::vector list2vec(struct string_list* cur) - { - sass::vector list; - while (cur) { - list.push_back(cur->string); - cur = cur->next; - } - return list; - } - -} - -extern "C" { - using namespace Sass; - - // Allocate libsass heap memory - // Don't forget string termination! - void* ADDCALL sass_alloc_memory(size_t size) - { - void* ptr = malloc(size); - if (ptr == NULL) { - std::cerr << "Out of memory.\n"; - exit(EXIT_FAILURE); - } - return ptr; - } - - char* ADDCALL sass_copy_c_string(const char* str) - { - if (str == nullptr) return nullptr; - size_t len = strlen(str) + 1; - char* cpy = (char*) sass_alloc_memory(len); - std::memcpy(cpy, str, len); - return cpy; - } - - // Deallocate libsass heap memory - void ADDCALL sass_free_memory(void* ptr) - { - if (ptr) free (ptr); - } - - // caller must free the returned memory - char* ADDCALL sass_string_quote (const char *str, const char quote_mark) - { - sass::string quoted = quote(str, quote_mark); - return sass_copy_c_string(quoted.c_str()); - } - - // caller must free the returned memory - char* ADDCALL sass_string_unquote (const char *str) - { - sass::string unquoted = unquote(str); - return sass_copy_c_string(unquoted.c_str()); - } - - char* ADDCALL sass_compiler_find_include (const char* file, struct Sass_Compiler* compiler) - { - // get the last import entry to get current base directory - Sass_Import_Entry import = sass_compiler_get_last_import(compiler); - const sass::vector& incs = compiler->cpp_ctx->include_paths; - // create the vector with paths to lookup - sass::vector paths(1 + incs.size()); - paths.push_back(File::dir_name(import->abs_path)); - paths.insert( paths.end(), incs.begin(), incs.end() ); - // now resolve the file path relative to lookup paths - sass::string resolved(File::find_include(file, paths)); - return sass_copy_c_string(resolved.c_str()); - } - - char* ADDCALL sass_compiler_find_file (const char* file, struct Sass_Compiler* compiler) - { - // get the last import entry to get current base directory - Sass_Import_Entry import = sass_compiler_get_last_import(compiler); - const sass::vector& incs = compiler->cpp_ctx->include_paths; - // create the vector with paths to lookup - sass::vector paths(1 + incs.size()); - paths.push_back(File::dir_name(import->abs_path)); - paths.insert( paths.end(), incs.begin(), incs.end() ); - // now resolve the file path relative to lookup paths - sass::string resolved(File::find_file(file, paths)); - return sass_copy_c_string(resolved.c_str()); - } - - // Make sure to free the returned value! - // Incs array has to be null terminated! - // this has the original resolve logic for sass include - char* ADDCALL sass_find_include (const char* file, struct Sass_Options* opt) - { - sass::vector vec(list2vec(opt->include_paths)); - sass::string resolved(File::find_include(file, vec)); - return sass_copy_c_string(resolved.c_str()); - } - - // Make sure to free the returned value! - // Incs array has to be null terminated! - char* ADDCALL sass_find_file (const char* file, struct Sass_Options* opt) - { - sass::vector vec(list2vec(opt->include_paths)); - sass::string resolved(File::find_file(file, vec)); - return sass_copy_c_string(resolved.c_str()); - } - - // Get compiled libsass version - const char* ADDCALL libsass_version(void) - { - return LIBSASS_VERSION; - } - - // Get compiled libsass version - const char* ADDCALL libsass_language_version(void) - { - return LIBSASS_LANGUAGE_VERSION; - } - -} - -namespace Sass { - - // helper to aid dreaded MSVC debug mode - char* sass_copy_string(sass::string str) - { - // In MSVC the following can lead to segfault: - // sass_copy_c_string(stream.str().c_str()); - // Reason is that the string returned by str() is disposed before - // sass_copy_c_string is invoked. The string is actually a stack - // object, so indeed nobody is holding on to it. So it seems - // perfectly fair to release it right away. So the const char* - // by c_str will point to invalid memory. I'm not sure if this is - // the behavior for all compiler, but I'm pretty sure we would - // have gotten more issues reported if that would be the case. - // Wrapping it in a functions seems the cleanest approach as the - // function must hold on to the stack variable until it's done. - return sass_copy_c_string(str.c_str()); - } - -} diff --git a/src/sass.hpp b/src/sass.hpp deleted file mode 100644 index f202bf79bf..0000000000 --- a/src/sass.hpp +++ /dev/null @@ -1,147 +0,0 @@ -// must be the first include in all compile units -#ifndef SASS_SASS_H -#define SASS_SASS_H - -// undefine extensions macro to tell sys includes -// that we do not want any macros to be exported -// mainly fixes an issue on SmartOS (SEC macro) -#undef __EXTENSIONS__ - -#ifdef _MSC_VER -#pragma warning(disable : 4005) -#endif - -// applies to MSVC and MinGW -#ifdef _WIN32 -// we do not want the ERROR macro -# ifndef NOGDI -# define NOGDI -# endif -// we do not want the min/max macro -# ifndef NOMINMAX -# define NOMINMAX -# endif -// we do not want the IN/OUT macro -# ifndef _NO_W32_PSEUDO_MODIFIERS -# define _NO_W32_PSEUDO_MODIFIERS -# endif -#endif - - -// should we be case insensitive -// when dealing with files or paths -#ifndef FS_CASE_SENSITIVE -# ifdef _WIN32 -# define FS_CASE_SENSITIVE 0 -# else -# define FS_CASE_SENSITIVE 1 -# endif -#endif - -// path separation char -#ifndef PATH_SEP -# ifdef _WIN32 -# define PATH_SEP ';' -# else -# define PATH_SEP ':' -# endif -#endif - - -// Include C-API header -#include "sass/base.h" - -// Include allocator -#include "memory.hpp" - -// For C++ helper -#include -#include - -// output behavior -namespace Sass { - - // create some C++ aliases for the most used options - const static Sass_Output_Style NESTED = SASS_STYLE_NESTED; - const static Sass_Output_Style COMPACT = SASS_STYLE_COMPACT; - const static Sass_Output_Style EXPANDED = SASS_STYLE_EXPANDED; - const static Sass_Output_Style COMPRESSED = SASS_STYLE_COMPRESSED; - // only used internal to trigger ruby inspect behavior - const static Sass_Output_Style INSPECT = SASS_STYLE_INSPECT; - const static Sass_Output_Style TO_SASS = SASS_STYLE_TO_SASS; - const static Sass_Output_Style TO_CSS = SASS_STYLE_TO_CSS; - - // helper to aid dreaded MSVC debug mode - // see implementation for more details - char* sass_copy_string(sass::string str); - -} - -// input behaviors -enum Sass_Input_Style { - SASS_CONTEXT_NULL, - SASS_CONTEXT_FILE, - SASS_CONTEXT_DATA, - SASS_CONTEXT_FOLDER -}; - -// simple linked list -struct string_list { - string_list* next; - char* string; -}; - -// sass config options structure -struct Sass_Inspect_Options { - - // Output style for the generated css code - // A value from above SASS_STYLE_* constants - enum Sass_Output_Style output_style; - - // Precision for fractional numbers - int precision; - - // initialization list (constructor with defaults) - Sass_Inspect_Options(Sass_Output_Style style = Sass::NESTED, - int precision = 10) - : output_style(style), precision(precision) - { } - -}; - -// sass config options structure -struct Sass_Output_Options : Sass_Inspect_Options { - - // String to be used for indentation - const char* indent; - // String to be used to for line feeds - const char* linefeed; - - // Emit comments in the generated CSS indicating - // the corresponding source line. - bool source_comments; - - // initialization list (constructor with defaults) - Sass_Output_Options(struct Sass_Inspect_Options opt, - const char* indent = " ", - const char* linefeed = "\n", - bool source_comments = false) - : Sass_Inspect_Options(opt), - indent(indent), linefeed(linefeed), - source_comments(source_comments) - { } - - // initialization list (constructor with defaults) - Sass_Output_Options(Sass_Output_Style style = Sass::NESTED, - int precision = 10, - const char* indent = " ", - const char* linefeed = "\n", - bool source_comments = false) - : Sass_Inspect_Options(style, precision), - indent(indent), linefeed(linefeed), - source_comments(source_comments) - { } - -}; - -#endif diff --git a/src/sass2scss.cpp b/src/sass2scss.cpp deleted file mode 100644 index ee6d55f7d3..0000000000 --- a/src/sass2scss.cpp +++ /dev/null @@ -1,895 +0,0 @@ -/** - * sass2scss - * Licensed under the MIT License - * Copyright (c) Marcel Greter - */ - -#ifdef _MSC_VER -#define _CRT_SECURE_NO_WARNINGS -#define _CRT_NONSTDC_NO_DEPRECATE -#endif - -// include library -#include -#include -#include -#include -#include -#include -#include - -///* -// -// src comments: comments in sass syntax (staring with //) -// css comments: multiline comments in css syntax (starting with /*) -// -// KEEP_COMMENT: keep src comments in the resulting css code -// STRIP_COMMENT: strip out all comments (either src or css) -// CONVERT_COMMENT: convert all src comments to css comments -// -//*/ - -// our own header -#include "sass2scss.h" - -// add namespace for c++ -namespace Sass -{ - - // return the actual prettify value from options - #define PRETTIFY(converter) (converter.options - (converter.options & 248)) - // query the options integer to check if the option is enables - #define KEEP_COMMENT(converter) ((converter.options & SASS2SCSS_KEEP_COMMENT) == SASS2SCSS_KEEP_COMMENT) - #define STRIP_COMMENT(converter) ((converter.options & SASS2SCSS_STRIP_COMMENT) == SASS2SCSS_STRIP_COMMENT) - #define CONVERT_COMMENT(converter) ((converter.options & SASS2SCSS_CONVERT_COMMENT) == SASS2SCSS_CONVERT_COMMENT) - - // some makros to access the indentation stack - #define INDENT(converter) (converter.indents.top()) - - // some makros to query comment parser status - #define IS_PARSING(converter) (converter.comment == "") - #define IS_COMMENT(converter) (converter.comment != "") - #define IS_SRC_COMMENT(converter) (converter.comment == "//" && ! CONVERT_COMMENT(converter)) - #define IS_CSS_COMMENT(converter) (converter.comment == "/*" || (converter.comment == "//" && CONVERT_COMMENT(converter))) - - // pretty printer helper function - static std::string closer (const converter& converter) - { - return PRETTIFY(converter) == 0 ? " }" : - PRETTIFY(converter) <= 1 ? " }" : - "\n" + INDENT(converter) + "}"; - } - - // pretty printer helper function - static std::string opener (const converter& converter) - { - return PRETTIFY(converter) == 0 ? " { " : - PRETTIFY(converter) <= 2 ? " {" : - "\n" + INDENT(converter) + "{"; - } - - // check if the given string is a pseudo selector - // needed to differentiate from sass property syntax - static bool isPseudoSelector (std::string& sel) - { - - size_t len = sel.length(); - if (len < 1) return false; - size_t pos = sel.find_first_not_of("abcdefghijklmnopqrstuvwxyz-ABCDEFGHIJKLMNOPQRSTUVWXYZ", 1); - if (pos != std::string::npos) sel.erase(pos, std::string::npos); - size_t i = sel.length(); - while (i -- > 0) { sel.at(i) = tolower(sel.at(i)); } - - // CSS Level 1 - Recommendation - if (sel == ":link") return true; - if (sel == ":visited") return true; - if (sel == ":active") return true; - - // CSS Level 2 (Revision 1) - Recommendation - if (sel == ":lang") return true; - if (sel == ":first-child") return true; - if (sel == ":hover") return true; - if (sel == ":focus") return true; - // disabled - also valid properties - // if (sel == ":left") return true; - // if (sel == ":right") return true; - if (sel == ":first") return true; - - // Selectors Level 3 - Recommendation - if (sel == ":target") return true; - if (sel == ":root") return true; - if (sel == ":nth-child") return true; - if (sel == ":nth-last-of-child") return true; - if (sel == ":nth-of-type") return true; - if (sel == ":nth-last-of-type") return true; - if (sel == ":last-child") return true; - if (sel == ":first-of-type") return true; - if (sel == ":last-of-type") return true; - if (sel == ":only-child") return true; - if (sel == ":only-of-type") return true; - if (sel == ":empty") return true; - if (sel == ":not") return true; - - // CSS Basic User Interface Module Level 3 - Working Draft - if (sel == ":default") return true; - if (sel == ":valid") return true; - if (sel == ":invalid") return true; - if (sel == ":in-range") return true; - if (sel == ":out-of-range") return true; - if (sel == ":required") return true; - if (sel == ":optional") return true; - if (sel == ":read-only") return true; - if (sel == ":read-write") return true; - if (sel == ":dir") return true; - if (sel == ":enabled") return true; - if (sel == ":disabled") return true; - if (sel == ":checked") return true; - if (sel == ":indeterminate") return true; - if (sel == ":nth-last-child") return true; - - // Selectors Level 4 - Working Draft - if (sel == ":any-link") return true; - if (sel == ":local-link") return true; - if (sel == ":scope") return true; - if (sel == ":active-drop-target") return true; - if (sel == ":valid-drop-target") return true; - if (sel == ":invalid-drop-target") return true; - if (sel == ":current") return true; - if (sel == ":past") return true; - if (sel == ":future") return true; - if (sel == ":placeholder-shown") return true; - if (sel == ":user-error") return true; - if (sel == ":blank") return true; - if (sel == ":nth-match") return true; - if (sel == ":nth-last-match") return true; - if (sel == ":nth-column") return true; - if (sel == ":nth-last-column") return true; - if (sel == ":matches") return true; - - // Fullscreen API - Living Standard - if (sel == ":fullscreen") return true; - - // not a pseudo selector - return false; - - } - - static size_t findFirstCharacter (std::string& sass, size_t pos) - { - return sass.find_first_not_of(SASS2SCSS_FIND_WHITESPACE, pos); - } - - static size_t findLastCharacter (std::string& sass, size_t pos) - { - return sass.find_last_not_of(SASS2SCSS_FIND_WHITESPACE, pos); - } - - static bool isUrl (std::string& sass, size_t pos) - { - return sass[pos] == 'u' && sass[pos+1] == 'r' && sass[pos+2] == 'l' && sass[pos+3] == '('; - } - - // check if there is some char data - // will ignore everything in comments - static bool hasCharData (std::string& sass) - { - - size_t col_pos = 0; - - while (true) - { - - // try to find some meaningful char - col_pos = sass.find_first_not_of(" \t\n\v\f\r", col_pos); - - // there was no meaningful char found - if (col_pos == std::string::npos) return false; - - // found a multiline comment opener - if (sass.substr(col_pos, 2) == "/*") - { - // find the multiline comment closer - col_pos = sass.find("*/", col_pos); - // maybe we did not find the closer here - if (col_pos == std::string::npos) return false; - // skip closer - col_pos += 2; - } - else - { - return true; - } - - } - - } - // EO hasCharData - - // find src comment opener - // correctly skips quoted strings - static size_t findCommentOpener (std::string& sass) - { - - size_t col_pos = 0; - bool apoed = false; - bool quoted = false; - bool comment = false; - size_t brackets = 0; - - while (col_pos != std::string::npos) - { - - // process all interesting chars - col_pos = sass.find_first_of("\"\'/\\*()", col_pos); - - // assertion for valid result - if (col_pos != std::string::npos) - { - char character = sass.at(col_pos); - - if (character == '(') - { - if (!quoted && !apoed) brackets ++; - } - else if (character == ')') - { - if (!quoted && !apoed) brackets --; - } - else if (character == '\"') - { - // invert quote bool - if (!apoed && !comment) quoted = !quoted; - } - else if (character == '\'') - { - // invert quote bool - if (!quoted && !comment) apoed = !apoed; - } - else if (col_pos > 0 && character == '/') - { - if (sass.at(col_pos - 1) == '*') - { - comment = false; - } - // next needs to be a slash too - else if (sass.at(col_pos - 1) == '/') - { - // only found if not in single or double quote, bracket or comment - if (!quoted && !apoed && !comment && brackets == 0) return col_pos - 1; - } - } - else if (character == '\\') - { - // skip next char if in quote - if (quoted || apoed) col_pos ++; - } - // this might be a comment opener - else if (col_pos > 0 && character == '*') - { - // opening a multiline comment - if (sass.at(col_pos - 1) == '/') - { - // we are now in a comment - if (!quoted && !apoed) comment = true; - } - } - - // skip char - col_pos ++; - - } - - } - // EO while - - return col_pos; - - } - // EO findCommentOpener - - // remove multiline comments from sass string - // correctly skips quoted strings - static std::string removeMultilineComment (std::string &sass) - { - - std::string clean = ""; - size_t col_pos = 0; - size_t open_pos = 0; - size_t close_pos = 0; - bool apoed = false; - bool quoted = false; - bool comment = false; - - // process sass til string end - while (col_pos != std::string::npos) - { - - // process all interesting chars - col_pos = sass.find_first_of("\"\'/\\*", col_pos); - - // assertion for valid result - if (col_pos != std::string::npos) - { - char character = sass.at(col_pos); - - // found quoted string delimiter - if (character == '\"') - { - if (!apoed && !comment) quoted = !quoted; - } - else if (character == '\'') - { - if (!quoted && !comment) apoed = !apoed; - } - // found possible comment closer - else if (character == '/') - { - // look back to see if it is actually a closer - if (comment && col_pos > 0 && sass.at(col_pos - 1) == '*') - { - close_pos = col_pos + 1; comment = false; - } - } - else if (character == '\\') - { - // skip escaped char - if (quoted || apoed) col_pos ++; - } - // this might be a comment opener - else if (character == '*') - { - // look back to see if it is actually an opener - if (!quoted && !apoed && col_pos > 0 && sass.at(col_pos - 1) == '/') - { - comment = true; open_pos = col_pos - 1; - clean += sass.substr(close_pos, open_pos - close_pos); - } - } - - // skip char - col_pos ++; - - } - - } - // EO while - - // add final parts (add half open comment text) - if (comment) clean += sass.substr(open_pos); - else clean += sass.substr(close_pos); - - // return string - return clean; - - } - // EO removeMultilineComment - - // right trim a given string - std::string rtrim(const std::string &sass) - { - std::string trimmed = sass; - size_t pos_ws = trimmed.find_last_not_of(" \t\n\v\f\r"); - if (pos_ws != std::string::npos) - { trimmed.erase(pos_ws + 1); } - else { trimmed.clear(); } - return trimmed; - } - // EO rtrim - - // flush whitespace and print additional text, but - // only print additional chars and buffer whitespace - std::string flush (std::string& sass, converter& converter) - { - - // return flushed - std::string scss = ""; - - // print whitespace buffer - scss += PRETTIFY(converter) > 0 ? - converter.whitespace : ""; - // reset whitespace buffer - converter.whitespace = ""; - - // remove possible newlines from string - size_t pos_right = sass.find_last_not_of("\n\r"); - if (pos_right == std::string::npos) return scss; - - // get the linefeeds from the string - std::string lfs = sass.substr(pos_right + 1); - sass = sass.substr(0, pos_right + 1); - - // find some source comment opener - size_t comment_pos = findCommentOpener(sass); - // check if there was a source comment - if (comment_pos != std::string::npos) - { - // convert comment (but only outside other coments) - if (CONVERT_COMMENT(converter) && !IS_COMMENT(converter)) - { - // convert to multiline comment - sass.at(comment_pos + 1) = '*'; - // add comment node to the whitespace - sass += " */"; - } - // not at line start - if (comment_pos > 0) - { - // also include whitespace before the actual comment opener - size_t ws_pos = sass.find_last_not_of(SASS2SCSS_FIND_WHITESPACE, comment_pos - 1); - comment_pos = ws_pos == std::string::npos ? 0 : ws_pos + 1; - } - if (!STRIP_COMMENT(converter)) - { - // add comment node to the whitespace - converter.whitespace += sass.substr(comment_pos); - } - else - { - // sass = removeMultilineComments(sass); - } - // update the actual sass code - sass = sass.substr(0, comment_pos); - } - - // add newline as getline discharged it - converter.whitespace += lfs + "\n"; - - // maybe remove any leading whitespace - if (PRETTIFY(converter) == 0) - { - // remove leading whitespace and update string - size_t pos_left = sass.find_first_not_of(SASS2SCSS_FIND_WHITESPACE); - if (pos_left != std::string::npos) sass = sass.substr(pos_left); - } - - // add flushed data - scss += sass; - - // return string - return scss; - - } - // EO flush - - // process a line of the sass text - std::string process (std::string& sass, converter& converter) - { - - // resulting string - std::string scss = ""; - - // strip multi line comments - if (STRIP_COMMENT(converter)) - { - sass = removeMultilineComment(sass); - } - - // right trim input - sass = rtrim(sass); - - // get position of first meaningful character in string - size_t pos_left = sass.find_first_not_of(SASS2SCSS_FIND_WHITESPACE); - - // special case for final run - if (converter.end_of_file) pos_left = 0; - - // maybe has only whitespace - if (pos_left == std::string::npos) - { - // just add complete whitespace - converter.whitespace += sass + "\n"; - } - // have meaningful first char - else - { - - // extract and store indentation string - std::string indent = sass.substr(0, pos_left); - - // check if current line starts a comment - std::string open = sass.substr(pos_left, 2); - - // line has less or same indentation - // finalize previous open parser context - if (indent.length() <= INDENT(converter).length()) - { - - // close multilinie comment - if (IS_CSS_COMMENT(converter)) - { - // check if comments will be stripped anyway - if (!STRIP_COMMENT(converter)) scss += " */"; - } - // close src comment comment - else if (IS_SRC_COMMENT(converter)) - { - // add a newline to avoid closer on same line - // this would put the bracket in the comment node - // no longer needed since we parse them correctly - // if (KEEP_COMMENT(converter)) scss += "\n"; - } - // close css properties - else if (converter.property) - { - // add closer unless in concat mode - if (!converter.comma) - { - // if there was no colon we have a selector - // looks like there were no inner properties - if (converter.selector) scss += " {}"; - // add final semicolon - else if (!converter.semicolon) scss += ";"; - } - } - - // reset comment state - converter.comment = ""; - - } - - // make sure we close every "higher" block - while (indent.length() < INDENT(converter).length()) - { - // pop stacked context - converter.indents.pop(); - // print close bracket - if (IS_PARSING(converter)) - { scss += closer(converter); } - else { scss += " */"; } - // reset comment state - converter.comment = ""; - } - - // reset converter state - converter.selector = false; - - // looks like some undocumented behavior ... - // https://github.com/mgreter/sass2scss/issues/29 - if (sass.substr(pos_left, 1) == "\\") { - converter.selector = true; - sass[pos_left] = ' '; - } - - // check if we have sass property syntax - if (sass.substr(pos_left, 1) == ":" && sass.substr(pos_left, 2) != "::") - { - - // default to a selector - // change back if property found - converter.selector = true; - // get position of first whitespace char - size_t pos_wspace = sass.find_first_of(SASS2SCSS_FIND_WHITESPACE, pos_left); - // assertion check for valid result - if (pos_wspace != std::string::npos) - { - // get the possible pseudo selector - std::string pseudo = sass.substr(pos_left, pos_wspace - pos_left); - // get position of the first real property value char - // pseudo selectors get this far, but have no actual value - size_t pos_value = sass.find_first_not_of(SASS2SCSS_FIND_WHITESPACE, pos_wspace); - // assertion check for valid result - if (pos_value != std::string::npos) - { - // only process if not (fallowed by a semicolon or is a pseudo selector) - if (!(sass.at(pos_value) == ':' || isPseudoSelector(pseudo))) - { - // create new string by interchanging the colon sign for property and value - sass = indent + sass.substr(pos_left + 1, pos_wspace - pos_left - 1) + ":" + sass.substr(pos_wspace); - // try to find a colon in the current line, but only ... - size_t pos_colon = sass.find_first_not_of(":", pos_left); - // assertion for valid result - if (pos_colon != std::string::npos) - { - // ... after the first word (skip beginning colons) - pos_colon = sass.find_first_of(":", pos_colon); - // it is a selector if there was no colon found - converter.selector = pos_colon == std::string::npos; - } - } - } - } - - // check if we have a BEM property (one colon and no selector) - if (sass.substr(pos_left, 1) == ":" && converter.selector == true) { - size_t pos_wspace = sass.find_first_of(SASS2SCSS_FIND_WHITESPACE, pos_left); - sass = indent + sass.substr(pos_left + 1, pos_wspace) + ":"; - } - - } - - // terminate some statements immediately - else if ( - sass.substr(pos_left, 5) == "@warn" || - sass.substr(pos_left, 6) == "@debug" || - sass.substr(pos_left, 6) == "@error" || - sass.substr(pos_left, 6) == "@value" || - sass.substr(pos_left, 8) == "@charset" || - sass.substr(pos_left, 10) == "@namespace" - ) { sass = indent + sass.substr(pos_left); } - // replace some specific sass shorthand directives (if not fallowed by a white space character) - else if (sass.substr(pos_left, 1) == "=") - { sass = indent + "@mixin " + sass.substr(pos_left + 1); } - else if (sass.substr(pos_left, 1) == "+") - { - // must be followed by a mixin call (no whitespace afterwards or at ending directly) - if (sass[pos_left+1] != 0 && sass[pos_left+1] != ' ' && sass[pos_left+1] != '\t') { - sass = indent + "@include " + sass.substr(pos_left + 1); - } - } - - // add quotes for import if needed - else if (sass.substr(pos_left, 7) == "@import") - { - // get positions for the actual import url - size_t pos_import = sass.find_first_of(SASS2SCSS_FIND_WHITESPACE, pos_left + 7); - size_t pos = sass.find_first_not_of(SASS2SCSS_FIND_WHITESPACE, pos_import); - size_t start = pos; - bool in_dqstr = false; - bool in_sqstr = false; - bool is_escaped = false; - do { - if (is_escaped) { - is_escaped = false; - } - else if (sass[pos] == '\\') { - is_escaped = true; - } - else if (sass[pos] == '"') { - if (!in_sqstr) in_dqstr = ! in_dqstr; - } - else if (sass[pos] == '\'') { - if (!in_dqstr) in_sqstr = ! in_sqstr; - } - else if (in_dqstr || in_sqstr) { - // skip over quoted stuff - } - else if (sass[pos] == ',' || sass[pos] == 0) { - if (sass[start] != '"' && sass[start] != '\'' && !isUrl(sass, start)) { - size_t end = findLastCharacter(sass, pos - 1) + 1; - sass = sass.replace(end, 0, "\""); - sass = sass.replace(start, 0, "\""); - pos += 2; - } - start = findFirstCharacter(sass, pos + 1); - } - } - while (sass[pos++] != 0); - - } - else if ( - sass.substr(pos_left, 7) != "@return" && - sass.substr(pos_left, 7) != "@extend" && - sass.substr(pos_left, 8) != "@include" && - sass.substr(pos_left, 8) != "@content" - ) { - - // probably a selector anyway - converter.selector = true; - // try to find first colon in the current line - size_t pos_colon = sass.find_first_of(":", pos_left); - // assertion that we have a colon - if (pos_colon != std::string::npos) - { - // it is not a selector if we have a space after a colon - if (sass[pos_colon+1] == ' ') converter.selector = false; - if (sass[pos_colon+1] == ' ') converter.selector = false; - } - - } - - // current line has more indentation - if (indent.length() >= INDENT(converter).length()) - { - // not in comment mode - if (IS_PARSING(converter)) - { - // has meaningful chars - if (hasCharData(sass)) - { - // is probably a property - // also true for selectors - converter.property = true; - } - } - } - // current line has more indentation - if (indent.length() > INDENT(converter).length()) - { - // not in comment mode - if (IS_PARSING(converter)) - { - // had meaningful chars - if (converter.property) - { - // print block opener - scss += opener(converter); - // push new stack context - converter.indents.push(""); - // store block indentation - INDENT(converter) = indent; - } - } - // is and will be a src comment - else if (!IS_CSS_COMMENT(converter)) - { - // scss does not allow multiline src comments - // therefore add forward slashes to all lines - sass.at(INDENT(converter).length()+0) = '/'; - // there is an edge case here if indentation - // is minimal (will overwrite the fist char) - sass.at(INDENT(converter).length()+1) = '/'; - // could code around that, but I dont' think - // this will ever be the cause for any trouble - } - } - - // line is opening a new comment - if (open == "/*" || open == "//") - { - // reset the property state - converter.property = false; - // close previous comment - if (IS_CSS_COMMENT(converter) && open != "") - { - if (!STRIP_COMMENT(converter) && !CONVERT_COMMENT(converter)) scss += " */"; - } - // force single line comments - // into a correct css comment - if (CONVERT_COMMENT(converter)) - { - if (IS_PARSING(converter)) - { sass.at(pos_left + 1) = '*'; } - } - // set comment flag - converter.comment = open; - - } - - // flush data only under certain conditions - if (!( - // strip css and src comments if option is set - (IS_COMMENT(converter) && STRIP_COMMENT(converter)) || - // strip src comment even if strip option is not set - // but only if the keep src comment option is not set - (IS_SRC_COMMENT(converter) && ! KEEP_COMMENT(converter)) - )) - { - // flush data and buffer whitespace - scss += flush(sass, converter); - } - - // get position of last meaningful char - size_t pos_right = sass.find_last_not_of(SASS2SCSS_FIND_WHITESPACE); - - // check for invalid result - if (pos_right != std::string::npos) - { - - // get the last meaningful char - std::string close = sass.substr(pos_right, 1); - - // check if next line should be concatenated (list mode) - converter.comma = IS_PARSING(converter) && close == ","; - converter.semicolon = IS_PARSING(converter) && close == ";"; - - // check if we have more than - // one meaningful char - if (pos_right > 0) - { - - // get the last two chars from string - std::string close = sass.substr(pos_right - 1, 2); - // update parser status for expicitly closed comment - if (close == "*/") converter.comment = ""; - - } - - } - // EO have meaningful chars from end - - } - // EO have meaningful chars from start - - // return scss - return scss; - - } - // EO process - - // read line with either CR, LF or CR LF format - // http://stackoverflow.com/a/6089413/1550314 - static std::istream& safeGetline(std::istream& is, std::string& t) - { - t.clear(); - - // The characters in the stream are read one-by-one using a std::streambuf. - // That is faster than reading them one-by-one using the std::istream. - // Code that uses streambuf this way must be guarded by a sentry object. - // The sentry object performs various tasks, - // such as thread synchronization and updating the stream state. - - std::istream::sentry se(is, true); - std::streambuf* sb = is.rdbuf(); - - for(;;) { - int c = sb->sbumpc(); - switch (c) { - case '\n': - return is; - case '\r': - if(sb->sgetc() == '\n') - sb->sbumpc(); - return is; - case EOF: - // Also handle the case when the last line has no line ending - if(t.empty()) - is.setstate(std::ios::eofbit); - return is; - default: - t += (char)c; - } - } - } - - // the main converter function for c++ - char* sass2scss (const std::string& sass, const int options) - { - - // local variables - std::string line; - std::string scss = ""; - std::stringstream stream(sass); - - // create converter variable - converter converter; - // initialise all options - converter.comma = false; - converter.property = false; - converter.selector = false; - converter.semicolon = false; - converter.end_of_file = false; - converter.comment = ""; - converter.whitespace = ""; - converter.indents.push(""); - converter.options = options; - - // read line by line and process them - while(safeGetline(stream, line) && !stream.eof()) - { scss += process(line, converter); } - - // create mutable string - std::string closer = ""; - // set the end of file flag - converter.end_of_file = true; - // process to close all open blocks - scss += process(closer, converter); - - // allocate new memory on the heap - // caller has to free it after use - char * cstr = (char*) malloc (scss.length() + 1); - // create a copy of the string - strcpy (cstr, scss.c_str()); - // return pointer - return &cstr[0]; - - } - // EO sass2scss - -} -// EO namespace - -// implement for c -extern "C" -{ - - char* ADDCALL sass2scss (const char* sass, const int options) - { - return Sass::sass2scss(sass, options); - } - - // Get compiled sass2scss version - const char* ADDCALL sass2scss_version(void) { - return SASS2SCSS_VERSION; - } - -} diff --git a/src/sass_context.cpp b/src/sass_context.cpp deleted file mode 100644 index 44cee57b63..0000000000 --- a/src/sass_context.cpp +++ /dev/null @@ -1,742 +0,0 @@ -// sass.hpp must go before all system headers to get the -// __EXTENSIONS__ fix on Solaris. -#include "sass.hpp" -#include "ast.hpp" - -#include "sass_functions.hpp" -#include "json.hpp" - -#define LFEED "\n" - -// C++ helper -namespace Sass { - // see sass_copy_c_string(sass::string str) - static inline JsonNode* json_mkstream(const sass::ostream& stream) - { - // hold on to string on stack! - sass::string str(stream.str()); - return json_mkstring(str.c_str()); - } - - static void handle_string_error(Sass_Context* c_ctx, const sass::string& msg, int severety) - { - sass::ostream msg_stream; - JsonNode* json_err = json_mkobject(); - msg_stream << "Internal Error: " << msg << std::endl; - json_append_member(json_err, "status", json_mknumber(severety)); - json_append_member(json_err, "message", json_mkstring(msg.c_str())); - json_append_member(json_err, "formatted", json_mkstream(msg_stream)); - try { c_ctx->error_json = json_stringify(json_err, " "); } - catch (...) {} - c_ctx->error_message = sass_copy_string(msg_stream.str()); - c_ctx->error_text = sass_copy_c_string(msg.c_str()); - c_ctx->error_status = severety; - c_ctx->output_string = 0; - c_ctx->source_map_string = 0; - json_delete(json_err); - } - - static int handle_error(Sass_Context* c_ctx) { - try { - throw; - } - catch (Exception::Base& e) { - sass::ostream msg_stream; - sass::string cwd(Sass::File::get_cwd()); - sass::string msg_prefix(e.errtype()); - bool got_newline = false; - msg_stream << msg_prefix << ": "; - const char* msg = e.what(); - while (msg && *msg) { - if (*msg == '\r') { - got_newline = true; - } - else if (*msg == '\n') { - got_newline = true; - } - else if (got_newline) { - msg_stream << sass::string(msg_prefix.size() + 2, ' '); - got_newline = false; - } - msg_stream << *msg; - ++msg; - } - if (!got_newline) msg_stream << "\n"; - - if (e.traces.empty()) { - // we normally should have some traces, still here as a fallback - sass::string rel_path(Sass::File::abs2rel(e.pstate.getPath(), cwd, cwd)); - msg_stream << sass::string(msg_prefix.size() + 2, ' '); - msg_stream << " on line " << e.pstate.getLine() << " of " << rel_path << "\n"; - } - else { - sass::string rel_path(Sass::File::abs2rel(e.pstate.getPath(), cwd, cwd)); - msg_stream << traces_to_string(e.traces, " "); - } - - // now create the code trace (ToDo: maybe have util functions?) - if (e.pstate.position.line != sass::string::npos && - e.pstate.position.column != sass::string::npos && - e.pstate.getRawData() != nullptr && - e.pstate.source != nullptr) { - Offset offset(e.pstate.position); - size_t lines = offset.line; - // scan through src until target line - // move line_beg pointer to line start - const char* line_beg; - for (line_beg = e.pstate.getRawData(); *line_beg != '\0'; ++line_beg) { - if (lines == 0) break; - if (*line_beg == '\n') --lines; - } - // move line_end before next newline character - const char* line_end; - for (line_end = line_beg; *line_end != '\0'; ++line_end) { - if (*line_end == '\n' || *line_end == '\r') break; - } - if (*line_end != '\0') ++line_end; - size_t line_len = line_end - line_beg; - size_t move_in = 0; size_t shorten = 0; - size_t left_chars = 42; size_t max_chars = 76; - // reported excerpt should not exceed `max_chars` chars - if (offset.column > line_len) left_chars = offset.column; - if (offset.column > left_chars) move_in = offset.column - left_chars; - if (line_len > max_chars + move_in) shorten = line_len - move_in - max_chars; - utf8::advance(line_beg, move_in, line_end); - utf8::retreat(line_end, shorten, line_beg); - sass::string sanitized; sass::string marker(offset.column - move_in, '-'); - utf8::replace_invalid(line_beg, line_end, std::back_inserter(sanitized)); - msg_stream << ">> " << sanitized << "\n"; - msg_stream << " " << marker << "^\n"; - } - - JsonNode* json_err = json_mkobject(); - json_append_member(json_err, "status", json_mknumber(1)); - json_append_member(json_err, "file", json_mkstring(e.pstate.getPath())); - json_append_member(json_err, "line", json_mknumber((double)(e.pstate.getLine()))); - json_append_member(json_err, "column", json_mknumber((double)(e.pstate.getColumn()))); - json_append_member(json_err, "message", json_mkstring(e.what())); - json_append_member(json_err, "formatted", json_mkstream(msg_stream)); - try { c_ctx->error_json = json_stringify(json_err, " "); } - catch (...) {} // silently ignore this error? - c_ctx->error_message = sass_copy_string(msg_stream.str()); - c_ctx->error_text = sass_copy_c_string(e.what()); - c_ctx->error_status = 1; - c_ctx->error_file = sass_copy_c_string(e.pstate.getPath()); - c_ctx->error_line = e.pstate.getLine(); - c_ctx->error_column = e.pstate.getColumn(); - c_ctx->error_src = sass_copy_c_string(e.pstate.getRawData()); - c_ctx->output_string = 0; - c_ctx->source_map_string = 0; - json_delete(json_err); - } - catch (std::bad_alloc& ba) { - sass::ostream msg_stream; - msg_stream << "Unable to allocate memory: " << ba.what(); - handle_string_error(c_ctx, msg_stream.str(), 2); - } - catch (std::exception& e) { - handle_string_error(c_ctx, e.what(), 3); - } - catch (sass::string& e) { - handle_string_error(c_ctx, e, 4); - } - catch (const char* e) { - handle_string_error(c_ctx, e, 4); - } - catch (...) { - handle_string_error(c_ctx, "unknown", 5); - } - return c_ctx->error_status; - } - - // allow one error handler to throw another error - // this can happen with invalid utf8 and json lib - static int handle_errors(Sass_Context* c_ctx) { - try { return handle_error(c_ctx); } - catch (...) { return handle_error(c_ctx); } - } - - static Block_Obj sass_parse_block(Sass_Compiler* compiler) throw() - { - - // assert valid pointer - if (compiler == 0) return {}; - // The cpp context must be set by now - Context* cpp_ctx = compiler->cpp_ctx; - Sass_Context* c_ctx = compiler->c_ctx; - // We will take care to wire up the rest - compiler->cpp_ctx->c_compiler = compiler; - compiler->state = SASS_COMPILER_PARSED; - - try { - - // get input/output path from options - sass::string input_path = safe_str(c_ctx->input_path); - sass::string output_path = safe_str(c_ctx->output_path); - - // maybe skip some entries of included files - // we do not include stdin for data contexts - bool skip = c_ctx->type == SASS_CONTEXT_DATA; - - // dispatch parse call - Block_Obj root(cpp_ctx->parse()); - // abort on errors - if (!root) return {}; - - // skip all prefixed files? (ToDo: check srcmap) - // IMO source-maps should point to headers already - // therefore don't skip it for now. re-enable or - // remove completely once this is tested - size_t headers = cpp_ctx->head_imports; - - // copy the included files on to the context (dont forget to free later) - if (copy_strings(cpp_ctx->get_included_files(skip, headers), &c_ctx->included_files) == NULL) - throw(std::bad_alloc()); - - // return parsed block - return root; - - } - // pass errors to generic error handler - catch (...) { handle_errors(c_ctx); } - - // error - return {}; - - } - -} - -extern "C" { - using namespace Sass; - - static void sass_clear_options (struct Sass_Options* options); - static void sass_reset_options (struct Sass_Options* options); - static void copy_options(struct Sass_Options* to, struct Sass_Options* from) { - // do not overwrite ourself - if (to == from) return; - // free assigned memory - sass_clear_options(to); - // move memory - *to = *from; - // Reset pointers on source - sass_reset_options(from); - } - - #define IMPLEMENT_SASS_OPTION_ACCESSOR(type, option) \ - type ADDCALL sass_option_get_##option (struct Sass_Options* options) { return options->option; } \ - void ADDCALL sass_option_set_##option (struct Sass_Options* options, type option) { options->option = option; } - #define IMPLEMENT_SASS_OPTION_STRING_GETTER(type, option, def) \ - type ADDCALL sass_option_get_##option (struct Sass_Options* options) { return safe_str(options->option, def); } - #define IMPLEMENT_SASS_OPTION_STRING_SETTER(type, option, def) \ - void ADDCALL sass_option_set_##option (struct Sass_Options* options, type option) \ - { free(options->option); options->option = option || def ? sass_copy_c_string(option ? option : def) : 0; } - #define IMPLEMENT_SASS_OPTION_STRING_ACCESSOR(type, option, def) \ - IMPLEMENT_SASS_OPTION_STRING_GETTER(type, option, def) \ - IMPLEMENT_SASS_OPTION_STRING_SETTER(type, option, def) - - #define IMPLEMENT_SASS_CONTEXT_GETTER(type, option) \ - type ADDCALL sass_context_get_##option (struct Sass_Context* ctx) { return ctx->option; } - #define IMPLEMENT_SASS_CONTEXT_TAKER(type, option) \ - type sass_context_take_##option (struct Sass_Context* ctx) \ - { type foo = ctx->option; ctx->option = 0; return foo; } - - - // generic compilation function (not exported, use file/data compile instead) - static Sass_Compiler* sass_prepare_context (Sass_Context* c_ctx, Context* cpp_ctx) throw() - { - try { - // register our custom functions - if (c_ctx->c_functions) { - auto this_func_data = c_ctx->c_functions; - while (this_func_data && *this_func_data) { - cpp_ctx->add_c_function(*this_func_data); - ++this_func_data; - } - } - - // register our custom headers - if (c_ctx->c_headers) { - auto this_head_data = c_ctx->c_headers; - while (this_head_data && *this_head_data) { - cpp_ctx->add_c_header(*this_head_data); - ++this_head_data; - } - } - - // register our custom importers - if (c_ctx->c_importers) { - auto this_imp_data = c_ctx->c_importers; - while (this_imp_data && *this_imp_data) { - cpp_ctx->add_c_importer(*this_imp_data); - ++this_imp_data; - } - } - - // reset error status - c_ctx->error_json = 0; - c_ctx->error_text = 0; - c_ctx->error_message = 0; - c_ctx->error_status = 0; - // reset error position - c_ctx->error_file = 0; - c_ctx->error_src = 0; - c_ctx->error_line = sass::string::npos; - c_ctx->error_column = sass::string::npos; - - // allocate a new compiler instance - void* ctxmem = calloc(1, sizeof(struct Sass_Compiler)); - if (ctxmem == 0) { std::cerr << "Error allocating memory for context" << std::endl; return 0; } - Sass_Compiler* compiler = (struct Sass_Compiler*) ctxmem; - compiler->state = SASS_COMPILER_CREATED; - - // store in sass compiler - compiler->c_ctx = c_ctx; - compiler->cpp_ctx = cpp_ctx; - cpp_ctx->c_compiler = compiler; - - // use to parse block - return compiler; - - } - // pass errors to generic error handler - catch (...) { handle_errors(c_ctx); } - - // error - return 0; - - } - - // generic compilation function (not exported, use file/data compile instead) - static int sass_compile_context (Sass_Context* c_ctx, Context* cpp_ctx) - { - - // prepare sass compiler with context and options - Sass_Compiler* compiler = sass_prepare_context(c_ctx, cpp_ctx); - - try { - // call each compiler step - sass_compiler_parse(compiler); - sass_compiler_execute(compiler); - } - // pass errors to generic error handler - catch (...) { handle_errors(c_ctx); } - - sass_delete_compiler(compiler); - - return c_ctx->error_status; - } - - inline void init_options (struct Sass_Options* options) - { - options->precision = 10; - options->indent = " "; - options->linefeed = LFEED; - } - - Sass_Options* ADDCALL sass_make_options (void) - { - struct Sass_Options* options = (struct Sass_Options*) calloc(1, sizeof(struct Sass_Options)); - if (options == 0) { std::cerr << "Error allocating memory for options" << std::endl; return 0; } - init_options(options); - return options; - } - - Sass_File_Context* ADDCALL sass_make_file_context(const char* input_path) - { - #ifdef DEBUG_SHARED_PTR - SharedObj::setTaint(true); - #endif - struct Sass_File_Context* ctx = (struct Sass_File_Context*) calloc(1, sizeof(struct Sass_File_Context)); - if (ctx == 0) { std::cerr << "Error allocating memory for file context" << std::endl; return 0; } - ctx->type = SASS_CONTEXT_FILE; - init_options(ctx); - try { - if (input_path == 0) { throw(std::runtime_error("File context created without an input path")); } - if (*input_path == 0) { throw(std::runtime_error("File context created with empty input path")); } - sass_option_set_input_path(ctx, input_path); - } catch (...) { - handle_errors(ctx); - } - return ctx; - } - - Sass_Data_Context* ADDCALL sass_make_data_context(char* source_string) - { - #ifdef DEBUG_SHARED_PTR - SharedObj::setTaint(true); - #endif - struct Sass_Data_Context* ctx = (struct Sass_Data_Context*) calloc(1, sizeof(struct Sass_Data_Context)); - if (ctx == 0) { std::cerr << "Error allocating memory for data context" << std::endl; return 0; } - ctx->type = SASS_CONTEXT_DATA; - init_options(ctx); - try { - if (source_string == 0) { throw(std::runtime_error("Data context created without a source string")); } - if (*source_string == 0) { throw(std::runtime_error("Data context created with empty source string")); } - ctx->source_string = source_string; - } catch (...) { - handle_errors(ctx); - } - return ctx; - } - - struct Sass_Compiler* ADDCALL sass_make_data_compiler (struct Sass_Data_Context* data_ctx) - { - if (data_ctx == 0) return 0; - Context* cpp_ctx = new Data_Context(*data_ctx); - return sass_prepare_context(data_ctx, cpp_ctx); - } - - struct Sass_Compiler* ADDCALL sass_make_file_compiler (struct Sass_File_Context* file_ctx) - { - if (file_ctx == 0) return 0; - Context* cpp_ctx = new File_Context(*file_ctx); - return sass_prepare_context(file_ctx, cpp_ctx); - } - - int ADDCALL sass_compile_data_context(Sass_Data_Context* data_ctx) - { - if (data_ctx == 0) return 1; - if (data_ctx->error_status) - return data_ctx->error_status; - try { - if (data_ctx->source_string == 0) { throw(std::runtime_error("Data context has no source string")); } - // empty source string is a valid case, even if not really useful (different than with file context) - // if (*data_ctx->source_string == 0) { throw(std::runtime_error("Data context has empty source string")); } - } - catch (...) { return handle_errors(data_ctx) | 1; } - Context* cpp_ctx = new Data_Context(*data_ctx); - return sass_compile_context(data_ctx, cpp_ctx); - } - - int ADDCALL sass_compile_file_context(Sass_File_Context* file_ctx) - { - if (file_ctx == 0) return 1; - if (file_ctx->error_status) - return file_ctx->error_status; - try { - if (file_ctx->input_path == 0) { throw(std::runtime_error("File context has no input path")); } - if (*file_ctx->input_path == 0) { throw(std::runtime_error("File context has empty input path")); } - } - catch (...) { return handle_errors(file_ctx) | 1; } - Context* cpp_ctx = new File_Context(*file_ctx); - return sass_compile_context(file_ctx, cpp_ctx); - } - - int ADDCALL sass_compiler_parse(struct Sass_Compiler* compiler) - { - if (compiler == 0) return 1; - if (compiler->state == SASS_COMPILER_PARSED) return 0; - if (compiler->state != SASS_COMPILER_CREATED) return -1; - if (compiler->c_ctx == NULL) return 1; - if (compiler->cpp_ctx == NULL) return 1; - if (compiler->c_ctx->error_status) - return compiler->c_ctx->error_status; - // parse the context we have set up (file or data) - compiler->root = sass_parse_block(compiler); - // success - return 0; - } - - int ADDCALL sass_compiler_execute(struct Sass_Compiler* compiler) - { - if (compiler == 0) return 1; - if (compiler->state == SASS_COMPILER_EXECUTED) return 0; - if (compiler->state != SASS_COMPILER_PARSED) return -1; - if (compiler->c_ctx == NULL) return 1; - if (compiler->cpp_ctx == NULL) return 1; - if (compiler->root.isNull()) return 1; - if (compiler->c_ctx->error_status) - return compiler->c_ctx->error_status; - compiler->state = SASS_COMPILER_EXECUTED; - Context* cpp_ctx = compiler->cpp_ctx; - Block_Obj root = compiler->root; - // compile the parsed root block - try { compiler->c_ctx->output_string = cpp_ctx->render(root); } - // pass catched errors to generic error handler - catch (...) { return handle_errors(compiler->c_ctx) | 1; } - // generate source map json and store on context - compiler->c_ctx->source_map_string = cpp_ctx->render_srcmap(); - // success - return 0; - } - - // helper function, not exported, only accessible locally - static void sass_reset_options (struct Sass_Options* options) - { - // free pointer before - // or copy/move them - options->input_path = 0; - options->output_path = 0; - options->plugin_path = 0; - options->include_path = 0; - options->source_map_file = 0; - options->source_map_root = 0; - options->c_functions = 0; - options->c_importers = 0; - options->c_headers = 0; - options->plugin_paths = 0; - options->include_paths = 0; - } - - // helper function, not exported, only accessible locally - static void sass_clear_options (struct Sass_Options* options) - { - if (options == 0) return; - // Deallocate custom functions, headers and imports - sass_delete_function_list(options->c_functions); - sass_delete_importer_list(options->c_importers); - sass_delete_importer_list(options->c_headers); - // Deallocate inc paths - if (options->plugin_paths) { - struct string_list* cur; - struct string_list* next; - cur = options->plugin_paths; - while (cur) { - next = cur->next; - free(cur->string); - free(cur); - cur = next; - } - } - // Deallocate inc paths - if (options->include_paths) { - struct string_list* cur; - struct string_list* next; - cur = options->include_paths; - while (cur) { - next = cur->next; - free(cur->string); - free(cur); - cur = next; - } - } - // Free options strings - free(options->input_path); - free(options->output_path); - free(options->plugin_path); - free(options->include_path); - free(options->source_map_file); - free(options->source_map_root); - // Reset our pointers - options->input_path = 0; - options->output_path = 0; - options->plugin_path = 0; - options->include_path = 0; - options->source_map_file = 0; - options->source_map_root = 0; - options->c_functions = 0; - options->c_importers = 0; - options->c_headers = 0; - options->plugin_paths = 0; - options->include_paths = 0; - } - - // helper function, not exported, only accessible locally - // sass_free_context is also defined in old sass_interface - static void sass_clear_context (struct Sass_Context* ctx) - { - if (ctx == 0) return; - // release the allocated memory (mostly via sass_copy_c_string) - if (ctx->output_string) free(ctx->output_string); - if (ctx->source_map_string) free(ctx->source_map_string); - if (ctx->error_message) free(ctx->error_message); - if (ctx->error_text) free(ctx->error_text); - if (ctx->error_json) free(ctx->error_json); - if (ctx->error_file) free(ctx->error_file); - if (ctx->error_src) free(ctx->error_src); - free_string_array(ctx->included_files); - // play safe and reset properties - ctx->output_string = 0; - ctx->source_map_string = 0; - ctx->error_message = 0; - ctx->error_text = 0; - ctx->error_json = 0; - ctx->error_file = 0; - ctx->error_src = 0; - ctx->included_files = 0; - // debug leaked memory - #ifdef DEBUG_SHARED_PTR - SharedObj::dumpMemLeaks(); - #endif - // now clear the options - sass_clear_options(ctx); - } - - void ADDCALL sass_delete_compiler (struct Sass_Compiler* compiler) - { - if (compiler == 0) { - return; - } - Context* cpp_ctx = compiler->cpp_ctx; - if (cpp_ctx) delete(cpp_ctx); - compiler->cpp_ctx = NULL; - compiler->c_ctx = NULL; - compiler->root = {}; - free(compiler); - } - - void ADDCALL sass_delete_options (struct Sass_Options* options) - { - sass_clear_options(options); free(options); - } - - // Deallocate all associated memory with file context - void ADDCALL sass_delete_file_context (struct Sass_File_Context* ctx) - { - // clear the context and free it - sass_clear_context(ctx); free(ctx); - } - // Deallocate all associated memory with data context - void ADDCALL sass_delete_data_context (struct Sass_Data_Context* ctx) - { - // clean the source string if it was not passed - // we reset this member once we start parsing - if (ctx->source_string) free(ctx->source_string); - // clear the context and free it - sass_clear_context(ctx); free(ctx); - } - - // Getters for sass context from specific implementations - struct Sass_Context* ADDCALL sass_file_context_get_context(struct Sass_File_Context* ctx) { return ctx; } - struct Sass_Context* ADDCALL sass_data_context_get_context(struct Sass_Data_Context* ctx) { return ctx; } - - // Getters for context options from Sass_Context - struct Sass_Options* ADDCALL sass_context_get_options(struct Sass_Context* ctx) { return ctx; } - struct Sass_Options* ADDCALL sass_file_context_get_options(struct Sass_File_Context* ctx) { return ctx; } - struct Sass_Options* ADDCALL sass_data_context_get_options(struct Sass_Data_Context* ctx) { return ctx; } - void ADDCALL sass_file_context_set_options (struct Sass_File_Context* ctx, struct Sass_Options* opt) { copy_options(ctx, opt); } - void ADDCALL sass_data_context_set_options (struct Sass_Data_Context* ctx, struct Sass_Options* opt) { copy_options(ctx, opt); } - - // Getters for Sass_Compiler options (get connected sass context) - enum Sass_Compiler_State ADDCALL sass_compiler_get_state(struct Sass_Compiler* compiler) { return compiler->state; } - struct Sass_Context* ADDCALL sass_compiler_get_context(struct Sass_Compiler* compiler) { return compiler->c_ctx; } - struct Sass_Options* ADDCALL sass_compiler_get_options(struct Sass_Compiler* compiler) { return compiler->c_ctx; } - // Getters for Sass_Compiler options (query import stack) - size_t ADDCALL sass_compiler_get_import_stack_size(struct Sass_Compiler* compiler) { return compiler->cpp_ctx->import_stack.size(); } - Sass_Import_Entry ADDCALL sass_compiler_get_last_import(struct Sass_Compiler* compiler) { return compiler->cpp_ctx->import_stack.back(); } - Sass_Import_Entry ADDCALL sass_compiler_get_import_entry(struct Sass_Compiler* compiler, size_t idx) { return compiler->cpp_ctx->import_stack[idx]; } - // Getters for Sass_Compiler options (query function stack) - size_t ADDCALL sass_compiler_get_callee_stack_size(struct Sass_Compiler* compiler) { return compiler->cpp_ctx->callee_stack.size(); } - Sass_Callee_Entry ADDCALL sass_compiler_get_last_callee(struct Sass_Compiler* compiler) { return &compiler->cpp_ctx->callee_stack.back(); } - Sass_Callee_Entry ADDCALL sass_compiler_get_callee_entry(struct Sass_Compiler* compiler, size_t idx) { return &compiler->cpp_ctx->callee_stack[idx]; } - - // Calculate the size of the stored null terminated array - size_t ADDCALL sass_context_get_included_files_size (struct Sass_Context* ctx) - { size_t l = 0; auto i = ctx->included_files; while (i && *i) { ++i; ++l; } return l; } - - // Create getter and setters for options - IMPLEMENT_SASS_OPTION_ACCESSOR(int, precision); - IMPLEMENT_SASS_OPTION_ACCESSOR(enum Sass_Output_Style, output_style); - IMPLEMENT_SASS_OPTION_ACCESSOR(bool, source_comments); - IMPLEMENT_SASS_OPTION_ACCESSOR(bool, source_map_embed); - IMPLEMENT_SASS_OPTION_ACCESSOR(bool, source_map_contents); - IMPLEMENT_SASS_OPTION_ACCESSOR(bool, source_map_file_urls); - IMPLEMENT_SASS_OPTION_ACCESSOR(bool, omit_source_map_url); - IMPLEMENT_SASS_OPTION_ACCESSOR(bool, is_indented_syntax_src); - IMPLEMENT_SASS_OPTION_ACCESSOR(Sass_Function_List, c_functions); - IMPLEMENT_SASS_OPTION_ACCESSOR(Sass_Importer_List, c_importers); - IMPLEMENT_SASS_OPTION_ACCESSOR(Sass_Importer_List, c_headers); - IMPLEMENT_SASS_OPTION_ACCESSOR(const char*, indent); - IMPLEMENT_SASS_OPTION_ACCESSOR(const char*, linefeed); - IMPLEMENT_SASS_OPTION_STRING_SETTER(const char*, plugin_path, 0); - IMPLEMENT_SASS_OPTION_STRING_SETTER(const char*, include_path, 0); - IMPLEMENT_SASS_OPTION_STRING_ACCESSOR(const char*, input_path, 0); - IMPLEMENT_SASS_OPTION_STRING_ACCESSOR(const char*, output_path, 0); - IMPLEMENT_SASS_OPTION_STRING_ACCESSOR(const char*, source_map_file, 0); - IMPLEMENT_SASS_OPTION_STRING_ACCESSOR(const char*, source_map_root, 0); - - // Create getter and setters for context - IMPLEMENT_SASS_CONTEXT_GETTER(int, error_status); - IMPLEMENT_SASS_CONTEXT_GETTER(const char*, error_json); - IMPLEMENT_SASS_CONTEXT_GETTER(const char*, error_message); - IMPLEMENT_SASS_CONTEXT_GETTER(const char*, error_text); - IMPLEMENT_SASS_CONTEXT_GETTER(const char*, error_file); - IMPLEMENT_SASS_CONTEXT_GETTER(const char*, error_src); - IMPLEMENT_SASS_CONTEXT_GETTER(size_t, error_line); - IMPLEMENT_SASS_CONTEXT_GETTER(size_t, error_column); - IMPLEMENT_SASS_CONTEXT_GETTER(const char*, output_string); - IMPLEMENT_SASS_CONTEXT_GETTER(const char*, source_map_string); - IMPLEMENT_SASS_CONTEXT_GETTER(char**, included_files); - - // Take ownership of memory (value on context is set to 0) - IMPLEMENT_SASS_CONTEXT_TAKER(char*, error_json); - IMPLEMENT_SASS_CONTEXT_TAKER(char*, error_message); - IMPLEMENT_SASS_CONTEXT_TAKER(char*, error_text); - IMPLEMENT_SASS_CONTEXT_TAKER(char*, error_file); - IMPLEMENT_SASS_CONTEXT_TAKER(char*, error_src); - IMPLEMENT_SASS_CONTEXT_TAKER(char*, output_string); - IMPLEMENT_SASS_CONTEXT_TAKER(char*, source_map_string); - IMPLEMENT_SASS_CONTEXT_TAKER(char**, included_files); - - // Push function for include paths (no manipulation support for now) - void ADDCALL sass_option_push_include_path(struct Sass_Options* options, const char* path) - { - - struct string_list* include_path = (struct string_list*) calloc(1, sizeof(struct string_list)); - if (include_path == 0) return; - include_path->string = path ? sass_copy_c_string(path) : 0; - struct string_list* last = options->include_paths; - if (!options->include_paths) { - options->include_paths = include_path; - } else { - while (last->next) - last = last->next; - last->next = include_path; - } - - } - - // Push function for include paths (no manipulation support for now) - size_t ADDCALL sass_option_get_include_path_size(struct Sass_Options* options) - { - size_t len = 0; - struct string_list* cur = options->include_paths; - while (cur) { len ++; cur = cur->next; } - return len; - } - - // Push function for include paths (no manipulation support for now) - const char* ADDCALL sass_option_get_include_path(struct Sass_Options* options, size_t i) - { - struct string_list* cur = options->include_paths; - while (i) { i--; cur = cur->next; } - return cur->string; - } - - // Push function for plugin paths (no manipulation support for now) - size_t ADDCALL sass_option_get_plugin_path_size(struct Sass_Options* options) - { - size_t len = 0; - struct string_list* cur = options->plugin_paths; - while (cur) { len++; cur = cur->next; } - return len; - } - - // Push function for plugin paths (no manipulation support for now) - const char* ADDCALL sass_option_get_plugin_path(struct Sass_Options* options, size_t i) - { - struct string_list* cur = options->plugin_paths; - while (i) { i--; cur = cur->next; } - return cur->string; - } - - // Push function for plugin paths (no manipulation support for now) - void ADDCALL sass_option_push_plugin_path(struct Sass_Options* options, const char* path) - { - - struct string_list* plugin_path = (struct string_list*) calloc(1, sizeof(struct string_list)); - if (plugin_path == 0) return; - plugin_path->string = path ? sass_copy_c_string(path) : 0; - struct string_list* last = options->plugin_paths; - if (!options->plugin_paths) { - options->plugin_paths = plugin_path; - } else { - while (last->next) - last = last->next; - last->next = plugin_path; - } - - } - -} diff --git a/src/sass_context.hpp b/src/sass_context.hpp deleted file mode 100644 index 16c8f16f51..0000000000 --- a/src/sass_context.hpp +++ /dev/null @@ -1,129 +0,0 @@ -#ifndef SASS_SASS_CONTEXT_H -#define SASS_SASS_CONTEXT_H - -#include "sass/base.h" -#include "sass/context.h" -#include "ast_fwd_decl.hpp" - -// sass config options structure -struct Sass_Options : Sass_Output_Options { - - // embed sourceMappingUrl as data uri - bool source_map_embed; - - // embed include contents in maps - bool source_map_contents; - - // create file urls for sources - bool source_map_file_urls; - - // Disable sourceMappingUrl in css output - bool omit_source_map_url; - - // Treat source_string as sass (as opposed to scss) - bool is_indented_syntax_src; - - // The input path is used for source map - // generation. It can be used to define - // something with string compilation or to - // overload the input file path. It is - // set to "stdin" for data contexts and - // to the input file on file contexts. - char* input_path; - - // The output path is used for source map - // generation. LibSass will not write to - // this file, it is just used to create - // information in source-maps etc. - char* output_path; - - // Colon-separated list of paths - // Semicolon-separated on Windows - // Maybe use array interface instead? - char* include_path; - char* plugin_path; - - // Include paths (linked string list) - struct string_list* include_paths; - // Plugin paths (linked string list) - struct string_list* plugin_paths; - - // Path to source map file - // Enables source map generation - // Used to create sourceMappingUrl - char* source_map_file; - - // Directly inserted in source maps - char* source_map_root; - - // Custom functions that can be called from sccs code - Sass_Function_List c_functions; - - // List of custom importers - Sass_Importer_List c_importers; - - // List of custom headers - Sass_Importer_List c_headers; - -}; - - -// base for all contexts -struct Sass_Context : Sass_Options -{ - - // store context type info - enum Sass_Input_Style type; - - // generated output data - char* output_string; - - // generated source map json - char* source_map_string; - - // error status - int error_status; - char* error_json; - char* error_text; - char* error_message; - // error position - char* error_file; - size_t error_line; - size_t error_column; - char* error_src; - - // report imported files - char** included_files; - -}; - -// struct for file compilation -struct Sass_File_Context : Sass_Context { - - // no additional fields required - // input_path is already on options - -}; - -// struct for data compilation -struct Sass_Data_Context : Sass_Context { - - // provided source string - char* source_string; - char* srcmap_string; - -}; - -// link c and cpp context -struct Sass_Compiler { - // progress status - Sass_Compiler_State state; - // original c context - Sass_Context* c_ctx; - // Sass::Context - Sass::Context* cpp_ctx; - // Sass::Block - Sass::Block_Obj root; -}; - -#endif diff --git a/src/sass_functions.cpp b/src/sass_functions.cpp deleted file mode 100644 index e576d47c44..0000000000 --- a/src/sass_functions.cpp +++ /dev/null @@ -1,210 +0,0 @@ -// sass.hpp must go before all system headers to get the -// __EXTENSIONS__ fix on Solaris. -#include "sass.hpp" - -#include -#include "util.hpp" -#include "context.hpp" -#include "values.hpp" -#include "sass/functions.h" -#include "sass_functions.hpp" - -extern "C" { - using namespace Sass; - - Sass_Function_List ADDCALL sass_make_function_list(size_t length) - { - return (Sass_Function_List) calloc(length + 1, sizeof(Sass_Function_Entry)); - } - - Sass_Function_Entry ADDCALL sass_make_function(const char* signature, Sass_Function_Fn function, void* cookie) - { - Sass_Function_Entry cb = (Sass_Function_Entry) calloc(1, sizeof(Sass_Function)); - if (cb == 0) return 0; - cb->signature = sass_copy_c_string(signature); - cb->function = function; - cb->cookie = cookie; - return cb; - } - - void ADDCALL sass_delete_function(Sass_Function_Entry entry) - { - free(entry->signature); - free(entry); - } - - // Deallocator for the allocated memory - void ADDCALL sass_delete_function_list(Sass_Function_List list) - { - Sass_Function_List it = list; - if (list == 0) return; - while(*list) { - sass_delete_function(*list); - ++list; - } - free(it); - } - - // Setters and getters for callbacks on function lists - Sass_Function_Entry ADDCALL sass_function_get_list_entry(Sass_Function_List list, size_t pos) { return list[pos]; } - void sass_function_set_list_entry(Sass_Function_List list, size_t pos, Sass_Function_Entry cb) { list[pos] = cb; } - - const char* ADDCALL sass_function_get_signature(Sass_Function_Entry cb) { return cb->signature; } - Sass_Function_Fn ADDCALL sass_function_get_function(Sass_Function_Entry cb) { return cb->function; } - void* ADDCALL sass_function_get_cookie(Sass_Function_Entry cb) { return cb->cookie; } - - Sass_Importer_Entry ADDCALL sass_make_importer(Sass_Importer_Fn importer, double priority, void* cookie) - { - Sass_Importer_Entry cb = (Sass_Importer_Entry) calloc(1, sizeof(Sass_Importer)); - if (cb == 0) return 0; - cb->importer = importer; - cb->priority = priority; - cb->cookie = cookie; - return cb; - } - - Sass_Importer_Fn ADDCALL sass_importer_get_function(Sass_Importer_Entry cb) { return cb->importer; } - double ADDCALL sass_importer_get_priority (Sass_Importer_Entry cb) { return cb->priority; } - void* ADDCALL sass_importer_get_cookie(Sass_Importer_Entry cb) { return cb->cookie; } - - // Just in case we have some stray import structs - void ADDCALL sass_delete_importer (Sass_Importer_Entry cb) - { - free(cb); - } - - // Creator for sass custom importer function list - Sass_Importer_List ADDCALL sass_make_importer_list(size_t length) - { - return (Sass_Importer_List) calloc(length + 1, sizeof(Sass_Importer_Entry)); - } - - // Deallocator for the allocated memory - void ADDCALL sass_delete_importer_list(Sass_Importer_List list) - { - Sass_Importer_List it = list; - if (list == 0) return; - while(*list) { - sass_delete_importer(*list); - ++list; - } - free(it); - } - - Sass_Importer_Entry ADDCALL sass_importer_get_list_entry(Sass_Importer_List list, size_t idx) { return list[idx]; } - void ADDCALL sass_importer_set_list_entry(Sass_Importer_List list, size_t idx, Sass_Importer_Entry cb) { list[idx] = cb; } - - // Creator for sass custom importer return argument list - Sass_Import_List ADDCALL sass_make_import_list(size_t length) - { - return (Sass_Import**) calloc(length + 1, sizeof(Sass_Import*)); - } - - // Creator for a single import entry returned by the custom importer inside the list - // We take ownership of the memory for source and srcmap (freed when context is destroyed) - Sass_Import_Entry ADDCALL sass_make_import(const char* imp_path, const char* abs_path, char* source, char* srcmap) - { - Sass_Import* v = (Sass_Import*) calloc(1, sizeof(Sass_Import)); - if (v == 0) return 0; - v->imp_path = imp_path ? sass_copy_c_string(imp_path) : 0; - v->abs_path = abs_path ? sass_copy_c_string(abs_path) : 0; - v->source = source; - v->srcmap = srcmap; - v->error = 0; - v->line = -1; - v->column = -1; - return v; - } - - // Older style, but somehow still valid - keep around or deprecate? - Sass_Import_Entry ADDCALL sass_make_import_entry(const char* path, char* source, char* srcmap) - { - return sass_make_import(path, path, source, srcmap); - } - - // Upgrade a normal import entry to throw an error (original path can be re-used by error reporting) - Sass_Import_Entry ADDCALL sass_import_set_error(Sass_Import_Entry import, const char* error, size_t line, size_t col) - { - if (import == 0) return 0; - if (import->error) free(import->error); - import->error = error ? sass_copy_c_string(error) : 0; - import->line = line ? line : -1; - import->column = col ? col : -1; - return import; - } - - // Setters and getters for entries on the import list - void ADDCALL sass_import_set_list_entry(Sass_Import_List list, size_t idx, Sass_Import_Entry entry) { list[idx] = entry; } - Sass_Import_Entry ADDCALL sass_import_get_list_entry(Sass_Import_List list, size_t idx) { return list[idx]; } - - // Deallocator for the allocated memory - void ADDCALL sass_delete_import_list(Sass_Import_List list) - { - Sass_Import_List it = list; - if (list == 0) return; - while(*list) { - sass_delete_import(*list); - ++list; - } - free(it); - } - - // Just in case we have some stray import structs - void ADDCALL sass_delete_import(Sass_Import_Entry import) - { - free(import->imp_path); - free(import->abs_path); - free(import->source); - free(import->srcmap); - free(import->error); - free(import); - } - - // Getter for callee entry - const char* ADDCALL sass_callee_get_name(Sass_Callee_Entry entry) { return entry->name; } - const char* ADDCALL sass_callee_get_path(Sass_Callee_Entry entry) { return entry->path; } - size_t ADDCALL sass_callee_get_line(Sass_Callee_Entry entry) { return entry->line; } - size_t ADDCALL sass_callee_get_column(Sass_Callee_Entry entry) { return entry->column; } - enum Sass_Callee_Type ADDCALL sass_callee_get_type(Sass_Callee_Entry entry) { return entry->type; } - Sass_Env_Frame ADDCALL sass_callee_get_env (Sass_Callee_Entry entry) { return &entry->env; } - - // Getters and Setters for environments (lexical, local and global) - union Sass_Value* ADDCALL sass_env_get_lexical (Sass_Env_Frame env, const char* name) { - Expression* ex = Cast((*env->frame)[name]); - return ex != NULL ? ast_node_to_sass_value(ex) : NULL; - } - void ADDCALL sass_env_set_lexical (Sass_Env_Frame env, const char* name, union Sass_Value* val) { - (*env->frame)[name] = sass_value_to_ast_node(val); - } - union Sass_Value* ADDCALL sass_env_get_local (Sass_Env_Frame env, const char* name) { - Expression* ex = Cast(env->frame->get_local(name)); - return ex != NULL ? ast_node_to_sass_value(ex) : NULL; - } - void ADDCALL sass_env_set_local (Sass_Env_Frame env, const char* name, union Sass_Value* val) { - env->frame->set_local(name, sass_value_to_ast_node(val)); - } - union Sass_Value* ADDCALL sass_env_get_global (Sass_Env_Frame env, const char* name) { - Expression* ex = Cast(env->frame->get_global(name)); - return ex != NULL ? ast_node_to_sass_value(ex) : NULL; - } - void ADDCALL sass_env_set_global (Sass_Env_Frame env, const char* name, union Sass_Value* val) { - env->frame->set_global(name, sass_value_to_ast_node(val)); - } - - // Getter for import entry - const char* ADDCALL sass_import_get_imp_path(Sass_Import_Entry entry) { return entry->imp_path; } - const char* ADDCALL sass_import_get_abs_path(Sass_Import_Entry entry) { return entry->abs_path; } - const char* ADDCALL sass_import_get_source(Sass_Import_Entry entry) { return entry->source; } - const char* ADDCALL sass_import_get_srcmap(Sass_Import_Entry entry) { return entry->srcmap; } - - // Getter for import error entry - size_t ADDCALL sass_import_get_error_line(Sass_Import_Entry entry) { return entry->line; } - size_t ADDCALL sass_import_get_error_column(Sass_Import_Entry entry) { return entry->column; } - const char* ADDCALL sass_import_get_error_message(Sass_Import_Entry entry) { return entry->error; } - - // Explicit functions to take ownership of the memory - // Resets our own property since we do not know if it is still alive - char* ADDCALL sass_import_take_source(Sass_Import_Entry entry) { char* ptr = entry->source; entry->source = 0; return ptr; } - char* ADDCALL sass_import_take_srcmap(Sass_Import_Entry entry) { char* ptr = entry->srcmap; entry->srcmap = 0; return ptr; } - -} diff --git a/src/sass_functions.hpp b/src/sass_functions.hpp deleted file mode 100644 index 482ed641b3..0000000000 --- a/src/sass_functions.hpp +++ /dev/null @@ -1,50 +0,0 @@ -#ifndef SASS_SASS_FUNCTIONS_H -#define SASS_SASS_FUNCTIONS_H - -#include "sass.h" -#include "environment.hpp" -#include "fn_utils.hpp" - -// Struct to hold custom function callback -struct Sass_Function { - char* signature; - Sass_Function_Fn function; - void* cookie; -}; - -// External import entry -struct Sass_Import { - char* imp_path; // path as found in the import statement - char *abs_path; // path after importer has resolved it - char* source; - char* srcmap; - // error handling - char* error; - size_t line; - size_t column; -}; - -// External environments -struct Sass_Env { - // links to parent frames - Sass::Env* frame; -}; - -// External call entry -struct Sass_Callee { - const char* name; - const char* path; - size_t line; - size_t column; - enum Sass_Callee_Type type; - struct Sass_Env env; -}; - -// Struct to hold importer callback -struct Sass_Importer { - Sass_Importer_Fn importer; - double priority; - void* cookie; -}; - -#endif \ No newline at end of file diff --git a/src/sass_values.cpp b/src/sass_values.cpp deleted file mode 100644 index 55bcb4d6a9..0000000000 --- a/src/sass_values.cpp +++ /dev/null @@ -1,362 +0,0 @@ -// sass.hpp must go before all system headers to get the -// __EXTENSIONS__ fix on Solaris. -#include "sass.hpp" - -#include -#include -#include "util.hpp" -#include "eval.hpp" -#include "operators.hpp" -#include "sass/values.h" -#include "sass_values.hpp" - -extern "C" { - using namespace Sass; - - // Return the sass tag for a generic sass value - enum Sass_Tag ADDCALL sass_value_get_tag(const union Sass_Value* v) { return v->unknown.tag; } - - // Check value for specified type - bool ADDCALL sass_value_is_null(const union Sass_Value* v) { return v->unknown.tag == SASS_NULL; } - bool ADDCALL sass_value_is_number(const union Sass_Value* v) { return v->unknown.tag == SASS_NUMBER; } - bool ADDCALL sass_value_is_string(const union Sass_Value* v) { return v->unknown.tag == SASS_STRING; } - bool ADDCALL sass_value_is_boolean(const union Sass_Value* v) { return v->unknown.tag == SASS_BOOLEAN; } - bool ADDCALL sass_value_is_color(const union Sass_Value* v) { return v->unknown.tag == SASS_COLOR; } - bool ADDCALL sass_value_is_list(const union Sass_Value* v) { return v->unknown.tag == SASS_LIST; } - bool ADDCALL sass_value_is_map(const union Sass_Value* v) { return v->unknown.tag == SASS_MAP; } - bool ADDCALL sass_value_is_error(const union Sass_Value* v) { return v->unknown.tag == SASS_ERROR; } - bool ADDCALL sass_value_is_warning(const union Sass_Value* v) { return v->unknown.tag == SASS_WARNING; } - - // Getters and setters for Sass_Number - double ADDCALL sass_number_get_value(const union Sass_Value* v) { return v->number.value; } - void ADDCALL sass_number_set_value(union Sass_Value* v, double value) { v->number.value = value; } - const char* ADDCALL sass_number_get_unit(const union Sass_Value* v) { return v->number.unit; } - void ADDCALL sass_number_set_unit(union Sass_Value* v, char* unit) { v->number.unit = unit; } - - // Getters and setters for Sass_String - const char* ADDCALL sass_string_get_value(const union Sass_Value* v) { return v->string.value; } - void ADDCALL sass_string_set_value(union Sass_Value* v, char* value) { v->string.value = value; } - bool ADDCALL sass_string_is_quoted(const union Sass_Value* v) { return v->string.quoted; } - void ADDCALL sass_string_set_quoted(union Sass_Value* v, bool quoted) { v->string.quoted = quoted; } - - // Getters and setters for Sass_Boolean - bool ADDCALL sass_boolean_get_value(const union Sass_Value* v) { return v->boolean.value; } - void ADDCALL sass_boolean_set_value(union Sass_Value* v, bool value) { v->boolean.value = value; } - - // Getters and setters for Sass_Color - double ADDCALL sass_color_get_r(const union Sass_Value* v) { return v->color.r; } - void ADDCALL sass_color_set_r(union Sass_Value* v, double r) { v->color.r = r; } - double ADDCALL sass_color_get_g(const union Sass_Value* v) { return v->color.g; } - void ADDCALL sass_color_set_g(union Sass_Value* v, double g) { v->color.g = g; } - double ADDCALL sass_color_get_b(const union Sass_Value* v) { return v->color.b; } - void ADDCALL sass_color_set_b(union Sass_Value* v, double b) { v->color.b = b; } - double ADDCALL sass_color_get_a(const union Sass_Value* v) { return v->color.a; } - void ADDCALL sass_color_set_a(union Sass_Value* v, double a) { v->color.a = a; } - - // Getters and setters for Sass_List - size_t ADDCALL sass_list_get_length(const union Sass_Value* v) { return v->list.length; } - enum Sass_Separator ADDCALL sass_list_get_separator(const union Sass_Value* v) { return v->list.separator; } - void ADDCALL sass_list_set_separator(union Sass_Value* v, enum Sass_Separator separator) { v->list.separator = separator; } - bool ADDCALL sass_list_get_is_bracketed(const union Sass_Value* v) { return v->list.is_bracketed; } - void ADDCALL sass_list_set_is_bracketed(union Sass_Value* v, bool is_bracketed) { v->list.is_bracketed = is_bracketed; } - // Getters and setters for Sass_List values - union Sass_Value* ADDCALL sass_list_get_value(const union Sass_Value* v, size_t i) { return v->list.values[i]; } - void ADDCALL sass_list_set_value(union Sass_Value* v, size_t i, union Sass_Value* value) { v->list.values[i] = value; } - - // Getters and setters for Sass_Map - size_t ADDCALL sass_map_get_length(const union Sass_Value* v) { return v->map.length; } - // Getters and setters for Sass_List keys and values - union Sass_Value* ADDCALL sass_map_get_key(const union Sass_Value* v, size_t i) { return v->map.pairs[i].key; } - union Sass_Value* ADDCALL sass_map_get_value(const union Sass_Value* v, size_t i) { return v->map.pairs[i].value; } - void ADDCALL sass_map_set_key(union Sass_Value* v, size_t i, union Sass_Value* key) { v->map.pairs[i].key = key; } - void ADDCALL sass_map_set_value(union Sass_Value* v, size_t i, union Sass_Value* val) { v->map.pairs[i].value = val; } - - // Getters and setters for Sass_Error - char* ADDCALL sass_error_get_message(const union Sass_Value* v) { return v->error.message; }; - void ADDCALL sass_error_set_message(union Sass_Value* v, char* msg) { v->error.message = msg; }; - - // Getters and setters for Sass_Warning - char* ADDCALL sass_warning_get_message(const union Sass_Value* v) { return v->warning.message; }; - void ADDCALL sass_warning_set_message(union Sass_Value* v, char* msg) { v->warning.message = msg; }; - - // Creator functions for all value types - - union Sass_Value* ADDCALL sass_make_boolean(bool val) - { - union Sass_Value* v = (Sass_Value*) calloc(1, sizeof(Sass_Value)); - if (v == 0) return 0; - v->boolean.tag = SASS_BOOLEAN; - v->boolean.value = val; - return v; - } - - union Sass_Value* ADDCALL sass_make_number(double val, const char* unit) - { - union Sass_Value* v = (Sass_Value*) calloc(1, sizeof(Sass_Value)); - if (v == 0) return 0; - v->number.tag = SASS_NUMBER; - v->number.value = val; - v->number.unit = unit ? sass_copy_c_string(unit) : 0; - if (v->number.unit == 0) { free(v); return 0; } - return v; - } - - union Sass_Value* ADDCALL sass_make_color(double r, double g, double b, double a) - { - union Sass_Value* v = (Sass_Value*) calloc(1, sizeof(Sass_Value)); - if (v == 0) return 0; - v->color.tag = SASS_COLOR; - v->color.r = r; - v->color.g = g; - v->color.b = b; - v->color.a = a; - return v; - } - - union Sass_Value* ADDCALL sass_make_string(const char* val) - { - union Sass_Value* v = (Sass_Value*) calloc(1, sizeof(Sass_Value)); - if (v == 0) return 0; - v->string.quoted = false; - v->string.tag = SASS_STRING; - v->string.value = val ? sass_copy_c_string(val) : 0; - if (v->string.value == 0) { free(v); return 0; } - return v; - } - - union Sass_Value* ADDCALL sass_make_qstring(const char* val) - { - union Sass_Value* v = (Sass_Value*) calloc(1, sizeof(Sass_Value)); - if (v == 0) return 0; - v->string.quoted = true; - v->string.tag = SASS_STRING; - v->string.value = val ? sass_copy_c_string(val) : 0; - if (v->string.value == 0) { free(v); return 0; } - return v; - } - - union Sass_Value* ADDCALL sass_make_list(size_t len, enum Sass_Separator sep, bool is_bracketed) - { - union Sass_Value* v = (Sass_Value*) calloc(1, sizeof(Sass_Value)); - if (v == 0) return 0; - v->list.tag = SASS_LIST; - v->list.length = len; - v->list.separator = sep; - v->list.is_bracketed = is_bracketed; - v->list.values = (union Sass_Value**) calloc(len, sizeof(union Sass_Value*)); - if (v->list.values == 0) { free(v); return 0; } - return v; - } - - union Sass_Value* ADDCALL sass_make_map(size_t len) - { - union Sass_Value* v = (Sass_Value*) calloc(1, sizeof(Sass_Value)); - if (v == 0) return 0; - v->map.tag = SASS_MAP; - v->map.length = len; - v->map.pairs = (struct Sass_MapPair*) calloc(len, sizeof(struct Sass_MapPair)); - if (v->map.pairs == 0) { free(v); return 0; } - return v; - } - - union Sass_Value* ADDCALL sass_make_null(void) - { - union Sass_Value* v = (Sass_Value*) calloc(1, sizeof(Sass_Value)); - if (v == 0) return 0; - v->null.tag = SASS_NULL; - return v; - } - - union Sass_Value* ADDCALL sass_make_error(const char* msg) - { - union Sass_Value* v = (Sass_Value*) calloc(1, sizeof(Sass_Value)); - if (v == 0) return 0; - v->error.tag = SASS_ERROR; - v->error.message = msg ? sass_copy_c_string(msg) : 0; - if (v->error.message == 0) { free(v); return 0; } - return v; - } - - union Sass_Value* ADDCALL sass_make_warning(const char* msg) - { - union Sass_Value* v = (Sass_Value*) calloc(1, sizeof(Sass_Value)); - if (v == 0) return 0; - v->warning.tag = SASS_WARNING; - v->warning.message = msg ? sass_copy_c_string(msg) : 0; - if (v->warning.message == 0) { free(v); return 0; } - return v; - } - - // will free all associated sass values - void ADDCALL sass_delete_value(union Sass_Value* val) { - - size_t i; - if (val == 0) return; - switch(val->unknown.tag) { - case SASS_NULL: { - } break; - case SASS_BOOLEAN: { - } break; - case SASS_NUMBER: { - free(val->number.unit); - } break; - case SASS_COLOR: { - } break; - case SASS_STRING: { - free(val->string.value); - } break; - case SASS_LIST: { - for (i=0; ilist.length; i++) { - sass_delete_value(val->list.values[i]); - } - free(val->list.values); - } break; - case SASS_MAP: { - for (i=0; imap.length; i++) { - sass_delete_value(val->map.pairs[i].key); - sass_delete_value(val->map.pairs[i].value); - } - free(val->map.pairs); - } break; - case SASS_ERROR: { - free(val->error.message); - } break; - case SASS_WARNING: { - free(val->error.message); - } break; - default: break; - } - - free(val); - - } - - // Make a deep cloned copy of the given sass value - union Sass_Value* ADDCALL sass_clone_value (const union Sass_Value* val) - { - - size_t i; - if (val == 0) return 0; - switch(val->unknown.tag) { - case SASS_NULL: { - return sass_make_null(); - } - case SASS_BOOLEAN: { - return sass_make_boolean(val->boolean.value); - } - case SASS_NUMBER: { - return sass_make_number(val->number.value, val->number.unit); - } - case SASS_COLOR: { - return sass_make_color(val->color.r, val->color.g, val->color.b, val->color.a); - } - case SASS_STRING: { - return sass_string_is_quoted(val) ? sass_make_qstring(val->string.value) : sass_make_string(val->string.value); - } - case SASS_LIST: { - union Sass_Value* list = sass_make_list(val->list.length, val->list.separator, val->list.is_bracketed); - for (i = 0; i < list->list.length; i++) { - list->list.values[i] = sass_clone_value(val->list.values[i]); - } - return list; - } - case SASS_MAP: { - union Sass_Value* map = sass_make_map(val->map.length); - for (i = 0; i < val->map.length; i++) { - map->map.pairs[i].key = sass_clone_value(val->map.pairs[i].key); - map->map.pairs[i].value = sass_clone_value(val->map.pairs[i].value); - } - return map; - } - case SASS_ERROR: { - return sass_make_error(val->error.message); - } - case SASS_WARNING: { - return sass_make_warning(val->warning.message); - } - default: break; - } - - return 0; - - } - - union Sass_Value* ADDCALL sass_value_stringify (const union Sass_Value* v, bool compressed, int precision) - { - ValueObj val = sass_value_to_ast_node(v); - Sass_Inspect_Options options(compressed ? COMPRESSED : NESTED, precision); - sass::string str(val->to_string(options)); - return sass_make_qstring(str.c_str()); - } - - union Sass_Value* ADDCALL sass_value_op (enum Sass_OP op, const union Sass_Value* a, const union Sass_Value* b) - { - - Sass::ValueObj rv; - - try { - - ValueObj lhs = sass_value_to_ast_node(a); - ValueObj rhs = sass_value_to_ast_node(b); - struct Sass_Inspect_Options options(NESTED, 5); - - // see if it's a relational expression - switch(op) { - case Sass_OP::EQ: return sass_make_boolean(Operators::eq(lhs, rhs)); - case Sass_OP::NEQ: return sass_make_boolean(Operators::neq(lhs, rhs)); - case Sass_OP::GT: return sass_make_boolean(Operators::gt(lhs, rhs)); - case Sass_OP::GTE: return sass_make_boolean(Operators::gte(lhs, rhs)); - case Sass_OP::LT: return sass_make_boolean(Operators::lt(lhs, rhs)); - case Sass_OP::LTE: return sass_make_boolean(Operators::lte(lhs, rhs)); - case Sass_OP::AND: return ast_node_to_sass_value(lhs->is_false() ? lhs : rhs); - case Sass_OP::OR: return ast_node_to_sass_value(lhs->is_false() ? rhs : lhs); - default: break; - } - - if (sass_value_is_number(a) && sass_value_is_number(b)) { - const Number* l_n = Cast(lhs); - const Number* r_n = Cast(rhs); - rv = Operators::op_numbers(op, *l_n, *r_n, options, l_n->pstate()); - } - else if (sass_value_is_number(a) && sass_value_is_color(a)) { - const Number* l_n = Cast(lhs); - // Direct HSLA operations are not supported - // All color maths will be deprecated anyway - Color_RGBA_Obj r_c = Cast(rhs)->toRGBA(); - rv = Operators::op_number_color(op, *l_n, *r_c, options, l_n->pstate()); - } - else if (sass_value_is_color(a) && sass_value_is_number(b)) { - // Direct HSLA operations are not supported - // All color maths will be deprecated anyway - Color_RGBA_Obj l_c = Cast(lhs)->toRGBA(); - const Number* r_n = Cast(rhs); - rv = Operators::op_color_number(op, *l_c, *r_n, options, l_c->pstate()); - } - else if (sass_value_is_color(a) && sass_value_is_color(b)) { - // Direct HSLA operations are not supported - // All color maths will be deprecated anyway - Color_RGBA_Obj l_c = Cast(lhs)->toRGBA(); - Color_RGBA_Obj r_c = Cast(rhs)->toRGBA(); - rv = Operators::op_colors(op, *l_c, *r_c, options, l_c->pstate()); - } - else /* convert other stuff to string and apply operation */ { - rv = Operators::op_strings(op, *lhs, *rhs, options, lhs->pstate()); - } - - // ToDo: maybe we should return null value? - if (!rv) return sass_make_error("invalid return value"); - - // convert result back to ast node - return ast_node_to_sass_value(rv.ptr()); - } - - // simply pass the error message back to the caller for now - catch (Exception::InvalidSass& e) { return sass_make_error(e.what()); } - catch (std::bad_alloc&) { return sass_make_error("memory exhausted"); } - catch (std::exception& e) { return sass_make_error(e.what()); } - catch (sass::string& e) { return sass_make_error(e.c_str()); } - catch (const char* e) { return sass_make_error(e); } - catch (...) { return sass_make_error("unknown"); } - } - -} diff --git a/src/sass_values.hpp b/src/sass_values.hpp deleted file mode 100644 index 9aa5cdb337..0000000000 --- a/src/sass_values.hpp +++ /dev/null @@ -1,82 +0,0 @@ -#ifndef SASS_SASS_VALUES_H -#define SASS_SASS_VALUES_H - -#include "sass.h" - -struct Sass_Unknown { - enum Sass_Tag tag; -}; - -struct Sass_Boolean { - enum Sass_Tag tag; - bool value; -}; - -struct Sass_Number { - enum Sass_Tag tag; - double value; - char* unit; -}; - -struct Sass_Color { - enum Sass_Tag tag; - double r; - double g; - double b; - double a; -}; - -struct Sass_String { - enum Sass_Tag tag; - bool quoted; - char* value; -}; - -struct Sass_List { - enum Sass_Tag tag; - enum Sass_Separator separator; - bool is_bracketed; - size_t length; - // null terminated "array" - union Sass_Value** values; -}; - -struct Sass_Map { - enum Sass_Tag tag; - size_t length; - struct Sass_MapPair* pairs; -}; - -struct Sass_Null { - enum Sass_Tag tag; -}; - -struct Sass_Error { - enum Sass_Tag tag; - char* message; -}; - -struct Sass_Warning { - enum Sass_Tag tag; - char* message; -}; - -union Sass_Value { - struct Sass_Unknown unknown; - struct Sass_Boolean boolean; - struct Sass_Number number; - struct Sass_Color color; - struct Sass_String string; - struct Sass_List list; - struct Sass_Map map; - struct Sass_Null null; - struct Sass_Error error; - struct Sass_Warning warning; -}; - -struct Sass_MapPair { - union Sass_Value* key; - union Sass_Value* value; -}; - -#endif diff --git a/src/scanner_string.cpp b/src/scanner_string.cpp new file mode 100644 index 0000000000..fa99b7f31c --- /dev/null +++ b/src/scanner_string.cpp @@ -0,0 +1,260 @@ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +/* Implementations are mostly a direct code-port from dart-sass. */ +/*****************************************************************************/ +#include "scanner_string.hpp" + +#include "charcode.hpp" +#include "character.hpp" +#include "exceptions.hpp" +#include "utf8/checked.h" + +namespace Sass { + + // Import some namespaces + using namespace Charcode; + using namespace Character; + + StringScanner::StringScanner( + Logger& logger, + SourceDataObj source) : + source(source), + startpos(source->contentStart()), + endpos(source->contentEnd()), + position(source->contentStart()), + sourceUrl(source->getAbsPath()), + srcid(source->getSrcIdx()), + offset(), + relevant(), + logger(logger) + { + // consume BOM? + + // This can use up to 3% runtime (mostly under 1%) + auto invalid = utf8::find_invalid(startpos, endpos); + if (invalid != endpos) { + SourceSpan pstate(source); + Offset start(startpos, invalid); + pstate.position.line = start.line; + pstate.position.column = start.column; + throw Exception::InvalidUnicode(pstate, {}); + } + } + + // Whether the scanner has completely consumed [string]. + bool StringScanner::isDone() const + { + return position >= endpos; + } + + // Called whenever a character is consumed. + // Used to update scanner line/column position. + void StringScanner::consumedChar(uint8_t character) + { + // std::cerr << "COnsumed [" << character << "]\n"; + switch (character) { + case $space: + case $tab: + case $vt: + case $ff: + case $cr: + offset.column += 1; + break; + case $lf: + offset.line += 1; + offset.column = 0; + break; + default: + // skip over 10xxxxxx and 01xxxxxx + // count ASCII and first utf8 bytes + if (Character::isCharacter(character)) { + // 64 => first utf8 byte + // 128 => regular ASCII char + offset.column += 1; + // sync relevant position + relevant = offset; + } + break; + } + } + + + // Consumes a single character and returns its character + // code. This throws a [FormatException] if the string has + // been fully consumed. It doesn't affect [lastMatch]. + uint8_t StringScanner::readChar() + { + if (isDone()) _fail("more input"); + uint8_t ascii = *position; + consumedChar(ascii); + position += 1; + return ascii; + } + + // Returns the character code of the character [offset] away + // from [position]. [offset] defaults to zero, and may be negative + // to inspect already-consumed characters. This returns `null` if + // [offset] points outside the string. It doesn't affect [lastMatch]. + uint8_t StringScanner::peekChar(size_t offset) const + { + const char* cur = position + offset; + if (cur < startpos || cur >= endpos) { + return 0; + } + return *cur; + } + + // Same as above, but stores next char into passed variable. + bool StringScanner::peekChar(uint8_t& chr, size_t offset) const + { + const char* cur = position + offset; + if (cur < startpos || cur >= endpos) { + return false; + } + chr = *cur; + return true; + } + + // If the next character in the string is [character], consumes it. + // Returns whether or not [character] was consumed. + bool StringScanner::scanChar(uint8_t character) + { + if (isDone()) return false; + uint8_t ascii = *position; + if (character != ascii) return false; + consumedChar(character); + position += 1; + return true; + } + + // If the next character in the string is [character], consumes it. + // If [character] could not be consumed, throws a [FormatException] + // describing the position of the failure. [name] is used in this + // error as the expected name of the character being matched; if + // it's empty, the character itself is used instead. + void StringScanner::expectChar(uint8_t character, const sass::string& name, bool advance) + { + if (!scanChar(character)) { + if (advance && !isDone()) { + relevant = offset; + } + if (name.empty()) { + if (character == $quote) { + _fail("\"\\\"\""); + } + else { + sass::string msg("\""); + msg += character; + _fail(msg + "\""); + } + } + _fail(name); + } + } + + // If [pattern] matches at the current position of the string, scans forward + // until the end of the match. Returns whether or not [pattern] matched. + bool StringScanner::scan(const sass::string& pattern) + { + const char* cur = position; + for (uint8_t code : pattern) { + if (isDone()) return false; + uint8_t ascii = *cur; + if (ascii != code) return false; + consumedChar(ascii); + cur += 1; + } + position = cur; + return true; + } + + // If [pattern] matches at the current position of the string, scans + // forward until the end of the match. If [pattern] did not match, + // throws a [FormatException] describing the position of the failure. + // [name] is used in this error as the expected name of the pattern + // being matched; if it's `null`, the pattern itself is used instead. + void StringScanner::expect(const sass::string& pattern, const sass::string& name) + { + if (!scan(pattern)) { + if (name.empty()) { + _fail(pattern); + } + _fail(name); + } + } + + // If the string has not been fully consumed, + // this throws a [FormatException]. + void StringScanner::expectDone() + { + if (isDone()) return; + SourceSpan span(rawSpan()); + callStackFrame frame(logger, span); + throw Exception::ParserException( + logger, "expected no more input."); + } + + // Returns whether or not [pattern] matches at the current position + // of the string. This doesn't move the scan pointer forward. + bool StringScanner::matches(const sass::string& pattern) + { + const char* cur = position; + for (char chr : pattern) { + if (chr != *cur) { + return false; + } + cur += 1; + } + return true; + } + + // Returns the substring of [string] between [start] and [end]. + // Unlike [String.substring], [end] defaults to [position] + // rather than the end of the string. + sass::string StringScanner::substring(const char* start, const char* end) + { + if (end == nullptr) end = position; + return sass::string(start, end); + } + + // Throws a [FormatException] describing that [name] is + // expected at the current position in the string. + void StringScanner::_fail( + const sass::string& name) const + { + SourceSpan span(relevantSpan()); + callStackFrame frame(logger, span); + sass::string msg("expected " + name + "."); + throw Exception::ParserException(logger, msg); + } + + // Throws a [FormatException] with [traces] and [pstate]. + void StringScanner::error( + const sass::string& message, + const BackTraces& traces, + const SourceSpan& pstate) const + { + callStackFrame frame(logger, pstate); + throw Exception::ParserException(traces, message); + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + bool StringScanner::hasLineBreak( + const char* before) const + { + const char* cur = before; + while (cur < endpos) { + if (*cur == '\r') return true; + if (*cur == '\n') return true; + cur += 1; + } + return false; + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + +} diff --git a/src/scanner_string.hpp b/src/scanner_string.hpp new file mode 100644 index 0000000000..b4c2aa5a1d --- /dev/null +++ b/src/scanner_string.hpp @@ -0,0 +1,195 @@ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#ifndef SASS_SCANNER_STRING_HPP +#define SASS_SCANNER_STRING_HPP + +// sass.hpp must go before all system headers +// to get the __EXTENSIONS__ fix on Solaris. +#include "capi_sass.hpp" + +#include "source.hpp" +#include "backtrace.hpp" + +namespace Sass { + + struct StringScannerState { + const char* position; + Offset offset; + }; + + // A class that scans through a string using [Pattern]s. + class StringScanner { + + public: + + // Value constructor + StringScanner( + Logger& logger, + SourceDataObj source); + + // The source associated with this scanner + SourceDataObj source; + + // The string being scanned through. + const char* startpos; + + // The final position to scan to. + const char* endpos; + + // The current position. + const char* position; + + // The URL of the source of the string being scanned. + // This is used for error reporting. It may be `null`, + // indicating that the source URL is unknown or unavailable. + const char* sourceUrl; + + // The global id for this input file. + size_t srcid; + + // The current line/col offset + Offset offset; + + // Last non whitespace position + // Used to create parser state spans + Offset relevant; + + // Attached logger + Logger& logger; + + // Whether the scanner has completely consumed [string]. + bool isDone() const; + + // Called whenever a character is consumed. + // Used to update scanner line/column position. + void consumedChar(uint8_t character); + + // Consumes a single character and returns its character + // code. This throws a [FormatException] if the string has + // been fully consumed. It doesn't affect [lastMatch]. + uint8_t readChar(); + + // Returns the character code of the character [offset] away + // from [position]. [offset] defaults to zero, and may be negative + // to inspect already-consumed characters. This returns `null` if + // [offset] points outside the string. It doesn't affect [lastMatch]. + uint8_t peekChar(size_t offset = 0) const; + + bool peekChar(uint8_t& chr, size_t offset = 0) const; + + // If the next character in the string is [character], consumes it. + // Returns whether or not [character] was consumed. + bool scanChar(uint8_t character); + + // If the next character in the string is [character], consumes it. + // If [character] could not be consumed, throws a [FormatException] + // describing the position of the failure. [name] is used in this + // error as the expected name of the character being matched; if + // it's `null`, the character itself is used instead. + void expectChar(uint8_t character, const sass::string& name = Strings::empty, bool advance = true); + + // If [pattern] matches at the current position of the string, scans forward + // until the end of the match. Returns whether or not [pattern] matched. + bool scan(const sass::string& pattern); + + // If [pattern] matches at the current position of the string, scans + // forward until the end of the match. If [pattern] did not match, + // throws a [FormatException] describing the position of the failure. + // [name] is used in this error as the expected name of the pattern + // being matched; if it's `null`, the pattern itself is used instead. + void expect(const sass::string& pattern, const sass::string& name = Strings::empty); + + // If the string has not been fully consumed, + // this throws a [FormatException]. + void expectDone(); + + // Returns whether or not [pattern] matches at the current position + // of the string. This doesn't move the scan pointer forward. + bool matches(const sass::string& pattern); + + // Returns the substring of [string] between [start] and [end]. + // Unlike [String.substring], [end] defaults to [position] + // rather than the end of the string. + sass::string substring(const char* start, const char* end = 0); + + // Throws a [FormatException] describing that [name] is + // expected at the current position in the string. + void _fail(const sass::string& name) const; + + // Throws a [FormatException] with [traces] and [pstate]. + void error(const sass::string& name, + const BackTraces& traces, + const SourceSpan& pstate) const; + + bool hasLineBreak(const char* before) const; + + StringScannerState state() { + return { position, offset }; + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // Backtrack the scanner to the given position + void backtrack(const StringScannerState& state) + { + position = state.position; + offset.line = state.offset.line; + offset.column = state.offset.column; + // We assume we always store states + // only from relevant positions. + relevant = offset; + } + + // Get a source span pointing to raw position + // Raw means whitespace may already be consumed + inline SourceSpan spanAt(Offset& start) const + { + SourceSpan pstate(source, start); + return source ? source->adjustSourceSpan(pstate) : pstate; + } + + // Get a source span pointing to raw position + // Raw means whitespace may already be consumed + inline SourceSpan rawSpan() const + { + SourceSpan pstate(source, offset); + return source ? source->adjustSourceSpan(pstate) : pstate; + } + + // Get a source span pointing to last relevant position + // Last relevant means whitespace is not yet parsed (word ending) + inline SourceSpan relevantSpan() const // 161 + { + SourceSpan pstate(source, relevant); + return source ? source->adjustSourceSpan(pstate) : pstate; + } + + // Create a source span from start to raw position + // Raw means whitespace may already be consumed + inline SourceSpan rawSpanFrom(const Offset& start) // 53 + { + SourceSpan pstate(source, start, Offset::distance(start, offset)); + return source ? source->adjustSourceSpan(pstate) : pstate; + } + + // Create a source span from start to last relevant position + // Last relevant means whitespace is not yet parsed (word ending) + inline SourceSpan relevantSpanFrom(const Offset& start) // 161 + { + SourceSpan pstate(source, start, Offset::distance(start, relevant)); + return source ? source->adjustSourceSpan(pstate) : pstate; + } + + inline SourceSpan relevantSpanFrom(const Offset& start, size_t delta) // 161 + { + SourceSpan pstate(source, start, Offset::distance(start, relevant)); + return source ? source->adjustSourceSpan(pstate) : pstate; + } + + }; + +} + +#endif diff --git a/src/sel_any.cpp b/src/sel_any.cpp new file mode 100644 index 0000000000..05b93185dc --- /dev/null +++ b/src/sel_any.cpp @@ -0,0 +1,72 @@ +#include "sel_any.hpp" + +#include "ast_selectors.hpp" + +namespace Sass { + + // Import some namespaces + using namespace Charcode; + using namespace Character; + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + bool AnySelectorVisitor::visitAttributeSelector(AttributeSelector* attribute) + { + return false; + } + + bool AnySelectorVisitor::visitClassSelector(ClassSelector* klass) + { + return false; + } + + bool AnySelectorVisitor::visitComplexSelector(ComplexSelector* complex) + { + for (auto cmpd : complex->elements()) { + if (cmpd->selector() == nullptr) continue; + if (cmpd->selector()->accept(this)) return true; + } + return false; + } + + bool AnySelectorVisitor::visitCompoundSelector(CompoundSelector* compound) + { + for (auto comp : compound->elements()) + if (comp->accept(this)) return true; + return false; + } + + bool AnySelectorVisitor::visitIDSelector(IDSelector* id) + { + return false; + } + + bool AnySelectorVisitor::visitPlaceholderSelector(PlaceholderSelector* placeholder) + { + return false; + } + + bool AnySelectorVisitor::visitPseudoSelector(PseudoSelector* pseudo) + { + if (pseudo->selector().isNull()) return false; + return pseudo->selector()->accept(this); + } + + bool AnySelectorVisitor::visitSelectorList(SelectorList* list) + { + for (auto cplx : list->elements()) + if (cplx->accept(this)) return true; + return false; + } + + bool AnySelectorVisitor::visitTypeSelector(TypeSelector* type) + { + return false; + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + +} + diff --git a/src/sel_any.hpp b/src/sel_any.hpp new file mode 100644 index 0000000000..4555979704 --- /dev/null +++ b/src/sel_any.hpp @@ -0,0 +1,33 @@ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#ifndef SASS_SEL_ANY_HPP +#define SASS_SEL_ANY_HPP + +#include "visitor_selector.hpp" + +namespace Sass { + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + class AnySelectorVisitor : public SelectorVisitor { + + public: + + virtual bool visitAttributeSelector(AttributeSelector* attribute) override final; + virtual bool visitClassSelector(ClassSelector* klass) override final; + virtual bool visitComplexSelector(ComplexSelector* complex) override; + virtual bool visitCompoundSelector(CompoundSelector* compound) override final; + virtual bool visitIDSelector(IDSelector* id) override final; + virtual bool visitPlaceholderSelector(PlaceholderSelector* placeholder) override; + virtual bool visitPseudoSelector(PseudoSelector* pseudo) override; + virtual bool visitSelectorList(SelectorList* list) override; + virtual bool visitTypeSelector(TypeSelector* type) override final; + // virtual bool visitSelectorCombinator(SelectorCombinator* combinator) override final; + + }; + +} + +#endif diff --git a/src/sel_bogus.cpp b/src/sel_bogus.cpp new file mode 100644 index 0000000000..bd217173e4 --- /dev/null +++ b/src/sel_bogus.cpp @@ -0,0 +1,59 @@ +#include "sel_bogus.hpp" + +#include "ast_selectors.hpp" + +namespace Sass { + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + bool IsBogusVisitor::visitComplexSelector(ComplexSelector* complex) + { + //std::cerr << "visit [" << complex->inspect() << "] " << includeLeadingCombinator << "\n"; + const auto& elements = complex->elements(); + if (elements.empty()) { + //std::cerr << "Case 1\n"; + return !complex->leadingCombinators().empty(); + } + else if (complex->leadingCombinators().size() + > (includeLeadingCombinator ? 0UL : 1UL)) + { + //std::cerr << "Case 2 " << includeLeadingCombinator << "\n"; + return true; + } + else if (!elements.back()->combinators().empty()) { + //std::cerr << "Case 3\n"; + return true; + } + else { + //std::cerr << "Case 4 " << includeLeadingCombinator << "\n"; + for (auto component : elements) { + if (component->combinators().size() > 1) return true; + return component->selector()->accept(this); + } + } + return false; + } + + bool IsBogusVisitor::visitPseudoSelector(PseudoSelector* pseudo) + { + auto& selector = pseudo->selector(); + if (selector.isNull()) return false; + + if (pseudo->name() != "has") return selector->isBogusStrict(); + else return selector->isBogusOtherThanLeadingCombinator(); + // return pseudo->selector()->accept(this); + return false; + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + const IsBogusVisitor IsBogusVisitorStrict(false); + const IsBogusVisitor IsBogusVisitorLenient(true); + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + +} + diff --git a/src/sel_bogus.hpp b/src/sel_bogus.hpp new file mode 100644 index 0000000000..6710aa120c --- /dev/null +++ b/src/sel_bogus.hpp @@ -0,0 +1,48 @@ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#ifndef SASS_SEL_BOGUS_HPP +#define SASS_SEL_BOGUS_HPP + +#include "visitor_selector.hpp" + +#include "sel_any.hpp" + +namespace Sass { + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + class IsBogusVisitor : public AnySelectorVisitor { + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + bool includeLeadingCombinator = false; + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + virtual bool visitComplexSelector(ComplexSelector* complex) override final; + virtual bool visitPseudoSelector(PseudoSelector* pseudo) override final; + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + public: + + IsBogusVisitor(bool includeLeadingCombinator) : + includeLeadingCombinator(includeLeadingCombinator) + {} + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + }; + + extern const IsBogusVisitor IsBogusVisitorStrict; + extern const IsBogusVisitor IsBogusVisitorLenient; + +} + +#endif diff --git a/src/sel_invisible.cpp b/src/sel_invisible.cpp new file mode 100644 index 0000000000..e4167a3873 --- /dev/null +++ b/src/sel_invisible.cpp @@ -0,0 +1,55 @@ +#include "sel_invisible.hpp" + +#include "ast_selectors.hpp" + +namespace Sass { + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + IsInvisibleVisitor::IsInvisibleVisitor( + bool includeBogus): + includeBogus(includeBogus) + { } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + bool IsInvisibleVisitor::visitSelectorList(SelectorList* list) + { + for (const auto& complex : list->elements()) { + if (!visitComplexSelector(complex)) return false; + } + return true; + } + + bool IsInvisibleVisitor::visitComplexSelector(ComplexSelector* complex) + { + bool asd = AnySelectorVisitor::visitComplexSelector(complex); + if (asd) { + //std::cerr << "base is invisible\n"; + } + return asd || + (includeBogus && complex->isBogusOtherThanLeadingCombinator()); + } + + bool IsInvisibleVisitor::visitPlaceholderSelector(PlaceholderSelector* placeholder) + { + return true; + } + + bool IsInvisibleVisitor::visitPseudoSelector(PseudoSelector* pseudo) + { + //std::cerr << "Visit Rule " << includeBogus << "\n"; + if (const auto& selector = pseudo->selector()) { + if (pseudo->name() != "not") return selector->accept(this); + else return includeBogus && selector->isBogusLenient(); + } + return false; + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + +} + diff --git a/src/sel_invisible.hpp b/src/sel_invisible.hpp new file mode 100644 index 0000000000..bac526877f --- /dev/null +++ b/src/sel_invisible.hpp @@ -0,0 +1,37 @@ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#ifndef SASS_SEL_INVISIBLE_HPP +#define SASS_SEL_INVISIBLE_HPP + +#include "visitor_selector.hpp" + +#include "sel_any.hpp" + +namespace Sass { + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + class IsInvisibleVisitor : public AnySelectorVisitor { + + /// Whether to consider selectors with bogus combinators invisible. + bool includeBogus; + + public: + + IsInvisibleVisitor(bool includeBogus); + + virtual bool visitSelectorList(SelectorList* list) override final; + virtual bool visitComplexSelector(ComplexSelector* complex) override; + virtual bool visitPlaceholderSelector(PlaceholderSelector* placeholder) override final; + virtual bool visitPseudoSelector(PseudoSelector* pseudo) override; + + }; + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + +} + +#endif diff --git a/src/sel_useless.cpp b/src/sel_useless.cpp new file mode 100644 index 0000000000..5a0730d47d --- /dev/null +++ b/src/sel_useless.cpp @@ -0,0 +1,29 @@ +#include "sel_useless.hpp" + +#include "ast_selectors.hpp" + +namespace Sass { + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + bool IsUselessVisitor::visitComplexSelector(ComplexSelector* complex) + { + if (complex->leadingCombinators().size() > 1) return true; + for (auto& component : complex->elements()) { + if (component->combinators().size() > 1) return true; + if (component->selector()->accept(this)) return true; + } + return false; + } + + bool IsUselessVisitor::visitPseudoSelector(PseudoSelector* pseudo) + { + return pseudo->isBogusLenient(); + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + +} + diff --git a/src/sel_useless.hpp b/src/sel_useless.hpp new file mode 100644 index 0000000000..22496de6e6 --- /dev/null +++ b/src/sel_useless.hpp @@ -0,0 +1,39 @@ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#ifndef SASS_SEL_USELESS_HPP +#define SASS_SEL_USELESS_HPP + +#include "visitor_selector.hpp" + +#include "sel_any.hpp" + +namespace Sass { + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + class IsUselessVisitor : public AnySelectorVisitor { + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + bool includeLeadingCombinator = false; + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + virtual bool visitComplexSelector(ComplexSelector* complex) override final; + virtual bool visitPseudoSelector(PseudoSelector* pseudo) override final; + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + }; + +} + +#endif diff --git a/src/settings.hpp b/src/settings.hpp index e4a1938ba9..3eb700384b 100644 --- a/src/settings.hpp +++ b/src/settings.hpp @@ -1,19 +1,120 @@ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ #ifndef SASS_SETTINGS_H #define SASS_SETTINGS_H // Global compile time settings should go here +// These settings are meant to be customized + +///////////////////////////////////////////////////////////////////////// +// Sass default settings +///////////////////////////////////////////////////////////////////////// + +// Default precision to format floats +#define SassDefaultPrecision 10 +#define SassDefaultEpsilon 10E-10 +#define SassDefaultNrSprintf "%.10f" + +///////////////////////////////////////////////////////////////////////// +// Hard-coded maximum nesting until we bail out. +// Note that this limit is not an exact science +// it depends on various factors, which some are +// not under our control (compile time or even OS +// dependent settings on the available stack size) +// It should fix most common segfault cases though. +///////////////////////////////////////////////////////////////////////// + +#ifndef SassMaxNesting +#define SassMaxNesting 512 +#endif + +///////////////////////////////////////////////////////////////////////// +// Should we preserve color information when possible +// E.g. if we create a HWB color with zero hue (which is always `gray`), +// dart-sass returns `50.196%` for its whiteness, no matter with which +// `whiteness` argument the HWB color was initialized. Even worse that +// `gray` would actually have a `whiteness` of exactly `50%`. But due +// to dart-sass internally clamping the rgb components to integer, the +// wrong `whiteness` of `50.1960784314%` is produced. LibSass tries to +// preserve the information for colors whenever possible, when doing +// transformations between formats it will not clamp the components. +///////////////////////////////////////////////////////////////////////// + +#ifndef SassPreserveColorInfo +#define SassPreserveColorInfo 1 +#endif + +///////////////////////////////////////////////////////////////////////// +// Sort map keys when outputting via inspect et al +///////////////////////////////////////////////////////////////////////// + +#ifndef SassSortMapKeysOnOutput +#define SassSortMapKeysOnOutput 0 +#endif + +///////////////////////////////////////////////////////////////////////// +// Error when extending compound selectors +///////////////////////////////////////////////////////////////////////// + +// Disable for older LibSass 3.6.3 behavior +#ifndef SassRestrictCompoundExtending +#define SassRestrictCompoundExtending 1 +#endif + +///////////////////////////////////////////////////////////////////////// +// Logger default settings +///////////////////////////////////////////////////////////////////////// + +// Default output character columns +#ifndef SassDefaultColumns +#define SassDefaultColumns 120 +#endif + +///////////////////////////////////////////////////////////////////////// +// Optional static hash seed +///////////////////////////////////////////////////////////////////////// + +// Define static hash seed (random otherwise) +// 0x9e3779b9 is the Fibonacci/Golden Ratio +// #define SassStaticHashSeed 0x9e3779b9 + +///////////////////////////////////////////////////////////////////////// +// Optimization configurations +///////////////////////////////////////////////////////////////////////// // When enabled we use our custom memory pool allocator // With intense workloads this can double the performance // Max memory usage mostly only grows by a slight amount -// #define SASS_CUSTOM_ALLOCATOR +// Brings up to 50% improvement with minor memory overhead. +#define SASS_CUSTOM_ALLOCATOR + +// Elide unnecessary value copies in eval, as we don't need to +// make copies already in the eval stage (I think). All further +// operations on those values will create a copy anyway! +// This applies to Number, String, Color and Boolean. +// Brings up to 5% improvement +#define SASS_ELIDE_COPIES -// How many buckets should we have for the free-list -// Determines when allocations go directly to malloc/free -// For maximum size of managed items multiply by alignment -#define SassAllocatorBuckets 512 +///////////////////////////////////////////////////////////////////////// +// Self assign optimization is experimental and may break your code +///////////////////////////////////////////////////////////////////////// +// Optimize self assign use-case where certain function would create +// a new copy and then assign to ourself, we can e.g. optimize map-merge +// or list-append in the following case: `$map = map-merge($map, $other)`. +// In order to do this we scan if on any assignment the right hand side +// is a function with the same variable as the first arguments. This flag +// is passed to the function when executed, so it knows to alter in-place. +///////////////////////////////////////////////////////////////////////// +#define SASS_OPTIMIZE_SELF_ASSIGN + +// Number of references until we can safely self assign. +// Set to a zero to practically disable this feature. +#ifndef AssignableRefCount +#define AssignableRefCount 3 +#endif -// The size of the memory pool arenas in bytes. -#define SassAllocatorArenaSize (1024 * 256) +///////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////// #endif diff --git a/src/shared_ptr.cpp b/src/shared_ptr.cpp new file mode 100644 index 0000000000..fb22ac9b26 --- /dev/null +++ b/src/shared_ptr.cpp @@ -0,0 +1,45 @@ +#include "shared_ptr.hpp" + +#ifdef DEBUG_SHARED_PTR +#include "debugger.hpp" +#include +#include +#endif + +#include "source.hpp" + +namespace Sass { + + #ifdef DEBUG_SHARED_PTR + void RefCounted::dumpMemLeaks() { + if (!all.empty()) { + std::cerr << "###################################\n"; + std::cerr << "# REPORTING MISSING DEALLOCATIONS #\n"; + std::cerr << "###################################\n"; + for (RefCounted* var : all) { + if (AstNode* ast = dynamic_cast(var)) { + std::cerr << "LEAKED AST " << ast->getDbgFile() << ":" << ast->getDbgLine() << "\n"; + debug_ast(ast); + } + else if (SourceData* ast = dynamic_cast(var)) { + std::cerr << "LEAKED SOURCE " << ast->getDbgFile() << ":" << ast->getDbgLine() << "[" << ast->getAbsPath() << "]\n"; + } + else { + std::cerr << "LEAKED " << var << "\n"; + } + } + all.clear(); + deleted.clear(); + objCount = 0; + } + } + size_t RefCounted::objCount = 0; + sass::vector RefCounted::all; + std::unordered_set RefCounted::deleted; + size_t RefCounted::maxRefCount = 0; +#endif + + bool RefCounted::taint = false; + // size_t RefCounted::moves = 0; + // size_t RefCounted::copies = 0; +} diff --git a/src/shared_ptr.hpp b/src/shared_ptr.hpp new file mode 100644 index 0000000000..740cd15432 --- /dev/null +++ b/src/shared_ptr.hpp @@ -0,0 +1,343 @@ +#ifndef SASS_MEMORY_SHARED_PTR_HPP +#define SASS_MEMORY_SHARED_PTR_HPP + +// sass.hpp must go before all system headers +// to get the __EXTENSIONS__ fix on Solaris. +#include "capi_sass.hpp" +#include "memory_allocator.hpp" + +#include +#include +#include +#include +#include + +#ifdef DEBUG_SHARED_PTR +#include +#endif + +// https://lokiastari.com/blog/2014/12/30/c-plus-plus-by-example-smart-pointer/index.html +// https://lokiastari.com/blog/2015/01/15/c-plus-plus-by-example-smart-pointer-part-ii/index.html +// https://lokiastari.com/blog/2015/01/23/c-plus-plus-by-example-smart-pointer-part-iii/index.html +// https://www.youtube.com/watch?v=LIb3L4vKZ7U - allocator composition (freelist and bucketizer) + +namespace Sass { + + template + class SharedPtr; + + /////////////////////////////////////////////////////////////////////////// + // Use macros for the allocation task, since overloading operator `new` + // has been proven to be flaky under certain compilers. This allows us + // to track memory issues better by adding the source location for every + // memory allocation, so once it leaks we know where it was created. + /////////////////////////////////////////////////////////////////////////// + + #ifdef DEBUG_SHARED_PTR + + // Macro to call the constructor + // Attach file/line in debug mode + #define SASS_MEMORY_NEW(Class, ...) \ + ((Class*)(new Class(__VA_ARGS__))->trace(__FILE__, __LINE__)) \ + + // Macro to call the constructor + // Attach file/line in debug mode + #define SASS_MEMORY_NEW_DBG(Class, ...) \ + ((Class*)(new Class(__VA_ARGS__))->trace(file, line)) \ + + // Macro to call the constructor + // Attach file/line in debug mode + #define SASS_MEMORY_NEW(Class, ...) \ + ((Class*)(new Class(__VA_ARGS__))->trace(__FILE__, __LINE__)) \ + + // Copy object and zero children + // The children array is empty + #define SASS_MEMORY_RESECT(obj) \ + ((obj)->copy(__FILE__, __LINE__, true)) \ + + // Full copy but same children + // Children are the same reference + #define SASS_MEMORY_COPY(obj) \ + ((obj)->copy(__FILE__, __LINE__, false)) \ + + #else + + // Macro to call the constructor + // Attach file/line in debug mode + #define SASS_MEMORY_NEW(Class, ...) \ + new Class(__VA_ARGS__) \ + + // Macro to call the constructor + // Attach file/line in debug mode + #define SASS_MEMORY_NEW_DBG(Class, ...) \ + new Class(__VA_ARGS__) \ + + // Copy object and zero children + // The children array is empty + #define SASS_MEMORY_RESECT(obj) \ + ((obj)->copy(true)) \ + + // Full copy but same children + // Children are the same reference + #define SASS_MEMORY_COPY(obj) \ + ((obj)->copy(false)) \ + + #endif + + // RefCounted 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 RefCounted { + + public: + RefCounted() : refcount(0) { + #ifdef DEBUG_SHARED_PTR + this->objId = ++objCount; + if (taint) { + all.emplace_back(this); + } + #endif + } + virtual ~RefCounted() { + #ifdef DEBUG_SHARED_PTR + for (size_t i = 0; i < all.size(); i++) { + if (all[i] == this) { + all.erase(all.begin() + i); + break; + } + } + erased = true; + #endif + } + + + #ifdef DEBUG_SHARED_PTR + static void reportRefCounts() { + std::cerr << "Max refcount: " << + RefCounted::maxRefCount << "\n"; + } + static void dumpMemLeaks(); + RefCounted* trace(sass::string file, size_t line) { + this->file = file; + this->line = line; + return this; + } + sass::string getDbgFile() { return file; } + size_t getDbgLine() { return line; } + void setDbg(bool dbg) { this->dbg = dbg; } + size_t getRefCount() const { return refcount; } + #endif + + static void setTaint(bool val) { taint = val; } + + #ifdef SASS_CUSTOM_ALLOCATOR + inline void* operator new(size_t nbytes) { + return allocateMem(nbytes); + } + inline void operator delete(void* ptr) { + return deallocateMem(ptr); + } + #endif + + protected: + friend class MemoryPool; + public: + public: + uint32_t refcount; + public: + static bool taint; +#ifdef DEBUG_SHARED_PTR + static size_t maxRefCount; + sass::string file; + size_t line; + public: + size_t objId; + public: + bool dbg = false; + bool erased = false; + static size_t objCount; + static sass::vector all; + static std::unordered_set deleted; +#endif + }; + + // SharedPtr is a intermediate (template-less) base class for SharedPtr. + // ToDo: there should be a way to include this in SharedPtr and to get + // ToDo: rid of all the static_cast that are now needed in SharedPtr. + template + class SharedPtr { + + protected: + + // We could also use `RefCounted*` instead, which would make life + // a bit easier with headers. Using `T*` means the implementation + // off that class must be known when using this in other classes. + T* node; + + private: + + static const uint32_t SET_DETACHED_BITMASK = (uint32_t(1) << (sizeof(uint32_t) * 8 - 1)); + static const uint32_t UNSET_DETACHED_BITMASK = ~(uint32_t(1) << (sizeof(uint32_t) * 8 - 1)); + + public: + SharedPtr() : node(nullptr) {} + SharedPtr(T* ptr) : node(ptr) { incRefCount(); } + SharedPtr(const SharedPtr& obj) : node(obj.node) { incRefCount(); } + SharedPtr(SharedPtr&& obj) noexcept : node(obj.node) { + obj.node = nullptr; // reset old node pointer + } + ~SharedPtr() { + decRefCount(); + } + + SharedPtr& operator=(T* other) + { + if (node != other) { + if (node) decRefCount(); + node = other; + incRefCount(); + } else if (node != nullptr) { + node->refcount &= UNSET_DETACHED_BITMASK; + } + return *this; + } + + SharedPtr& operator=(const SharedPtr& obj) + { + return *this = obj.node; + } + + SharedPtr& operator=(SharedPtr&& obj) noexcept + { + if (node != obj.node) { + if (node) decRefCount(); + node = obj.node; + obj.node = nullptr; + } + else if (node != nullptr) { + node->refcount &= UNSET_DETACHED_BITMASK; + } + return *this; + } + + // Prevents all SharedPtrs from freeing this node + // until it is assigned to any other SharedPtr. + T* detach() + { + if (node != nullptr) { + node->refcount |= SET_DETACHED_BITMASK; + } + #ifdef DEBUG_SHARED_PTR + if (node && node->dbg) { + std::cerr << "DETACHING NODE\n"; + } + #endif + return node; + } + + void clear() { + if (node != nullptr) { + decRefCount(); + node = nullptr; + } + } + + T* ptr() const { + #ifdef DEBUG_SHARED_PTR + if (node && node->deleted.size() && node->deleted.count(node->objId) == 1) { + std::cerr << "ACCESSING DELETED " << node << "\n"; + } + #endif + return node; + } + + T* operator->() const { + #ifdef DEBUG_SHARED_PTR + if (node && node->deleted.size() && node->deleted.count(node->objId) == 1) { + std::cerr << "ACCESSING DELETED " << node << "\n"; + } + #endif + return node; + } + + operator T* () const { return ptr(); } + operator T& () const { return *ptr(); } + T& operator* () const { return *ptr(); }; + + bool isNull() const { return node == nullptr; } + + protected: + + // ##__declspec(noinline) + inline void decRefCount() noexcept { + if (node == nullptr) return; + #ifdef DEBUG_SHARED_PTR + if (node->dbg) { + std::cerr << "- " << node << " X " << ((node->refcount & SET_DETACHED_BITMASK) ? "detached " : "") + << (node->refcount - (node->refcount & SET_DETACHED_BITMASK)) << " (" << this << ") " << "\n"; + } + #endif + if (--node->refcount == 0) { + #ifdef DEBUG_SHARED_PTR + if (node->dbg) { + std::cerr << "DELETE NODE " << node << "\n"; + } + // node->deleted.insert(node->objId); + #endif + delete node; + } + #ifdef DEBUG_SHARED_PTR + else if (node->refcount & SET_DETACHED_BITMASK) { + if (node->dbg) { + std::cerr << "NODE EVADED DELETE " << node << "\n"; + } + } + #endif + } + void incRefCount() noexcept { + if (node == nullptr) return; + node->refcount &= UNSET_DETACHED_BITMASK; + ++node->refcount; + #ifdef DEBUG_SHARED_PTR + if (RefCounted::maxRefCount < node->refcount) { + RefCounted::maxRefCount = node->refcount; + } + if (node->dbg) { + std::cerr << "+ " << node << " X " << node->refcount << " (" << this << ") " << "\n"; + } + #endif + } + + // template + // SharedPtr& operator=(U *rhs) { + // return static_cast&>( + // SharedPtr::operator=(static_cast(rhs))); + // } + // + // template + // SharedPtr& operator=(SharedPtr&& rhs) { + // return static_cast&>( + // SharedPtr::operator=(std::move(static_cast&>(rhs)))); + // } + // + // template + // SharedPtr& operator=(const SharedPtr& rhs) { + // return static_cast&>( + // SharedPtr::operator=(static_cast&>(rhs))); + // } + + }; + + // Comparison operators, based on: + // https://en.cppreference.com/w/cpp/memory/unique_ptr/operator_cmp + +} // namespace Sass + +#endif diff --git a/src/source.cpp b/src/source.cpp index 1ffab30b94..99088e14f8 100644 --- a/src/source.cpp +++ b/src/source.cpp @@ -1,69 +1,325 @@ -#include -#include -#include "source.hpp" -#include "utf8/checked.h" -#include "position.hpp" +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#include "sources.hpp" + +#include +#include "unicode.hpp" +#include "charcode.hpp" +#include "character.hpp" +#include "source_span.hpp" namespace Sass { + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // Import some namespaces + using namespace Charcode; + using namespace Character; + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // Base class constructor SourceData::SourceData() - : SharedObj() + : RefCounted() + {} + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // Value move constructor + SourceWithPath::SourceWithPath( + sass::string&& imp_path, + sass::string&& abs_path, + size_t idx) : + imp_path(std::move(imp_path)), + abs_path(std::move(abs_path)), + len_content(0), + len_srcmaps(0), + srcidx(idx), + lfs() + {} + + // Value copy constructor + SourceWithPath::SourceWithPath( + const sass::string& imp_path, + const sass::string& abs_path, + size_t idx) : + imp_path(imp_path), + abs_path(abs_path), + len_content(0), + len_srcmaps(0), + srcidx(idx), + lfs() + {} + + // Returns the number of lines. On first call + // it will calculate the linefeed lookup table. + // Standard implementation for raw char API + size_t SourceWithPath::countLines() + { + if (lfs.empty()) { + size_t len = 0; + lfs.emplace_back(len); + const char* data = content(); + while (data[len] != 0) { + // is a carriage return? + if (data[len] == $cr) { + if (data[len + 1] == $lf) { + lfs.emplace_back(len + 2); + ++len; // advance twice + } + else { + lfs.emplace_back(len + 1); + } + } + else if (data[len] == $lf) { + if (data[len + 1] == $cr) { + lfs.emplace_back(len + 2); + ++len; // advance twice + } + else { + lfs.emplace_back(len + 1); + } + } + ++len; + } + lfs.emplace_back(len); + } + + return lfs.size() - 1; + } + + // Returns the requested line. Will take interpolations into + // account to show more accurate debug messages. Calling this + // can be rather expensive, so only use it for debugging. + // Standard implementation for raw char API + sass::string SourceWithPath::getLine(size_t line) { + countLines(); + if (line > lfs.size()) { + return sass::string(); + } + size_t first = lfs[line]; + size_t last = lfs[line + 1]; + if (first == last) return sass::string(); + const char* beg = content() + first; + const char* end = content() + last; + if (end[-1] == $lf) end -= 1; + if (end[-1] == $cr) end -= 1; + return sass::string(beg, end); } + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // Value copy/move constructor + // Copied: imp_path and abs_path + // Moved: content and srcmaps data SourceFile::SourceFile( - const char* path, - const char* data, - size_t srcid) : - SourceData(), - path(sass_copy_c_string(path)), - data(sass_copy_c_string(data)), - length(0), - srcid(srcid) + const char* imp_path, + const char* abs_path, + char* content, + char* srcmaps, + size_t srcidx) : + SourceWithPath( + imp_path ? imp_path : "", + abs_path ? abs_path : "", + srcidx + ), + _content(content), + _srcmaps(srcmaps) { - length = strlen(data); + if (_content != nullptr) { + len_content = ::strlen(_content); + } + if (_srcmaps != nullptr) { + len_srcmaps = ::strlen(_srcmaps); + } } + // Only one that has to clean-up SourceFile::~SourceFile() { - sass_free_memory(path); - sass_free_memory(data); + sass_free_memory(_content); + sass_free_memory(_srcmaps); } - const char* SourceFile::end() const + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // Value move constructor without srcmaps + // ToDo: should we try to parse srcmaps? + SourceString::SourceString( + const char* abs_path, + sass::string&& content) : + SourceWithPath( + abs_path ? abs_path : "", + abs_path ? abs_path : "", + sass::string::npos + ), + _content(std::move(content)) { - return data + length; + len_content = _content.length(); } - const char* SourceFile::begin() const + // Value move constructor with srcmaps + SourceString::SourceString( + const char* imp_path, + const char* abs_path, + sass::string&& content, + sass::string&& srcmaps, + size_t srcidx) : + SourceWithPath( + imp_path ? imp_path : "", + abs_path ? abs_path : "", + srcidx + ), + _content(std::move(content)), + _srcmaps(std::move(srcmaps)) { - return data; + len_content = _content.length(); + len_srcmaps = _srcmaps.length(); } - const char* SourceFile::getRawData() const + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // Create a synthetic interpolated source. The `data` is the + // evaluated interpolation, while `around` is the original source + // where the actual interpolation was given at `pstate` position. + SourceItpl::SourceItpl(SourceSpan pstate, + sass::string&& data) : + SourceString( + pstate.getImpPath(), + pstate.getAbsPath(), + std::move(data), "", + pstate.getSrcIdx()), + pstate(pstate) { - return data; } - SourceSpan SourceFile::getSourceSpan() + // Returns adjusted source span with interpolation in mind. + // The input `pstate` is relative to the interpolation, will + // return a source span with absolute position in regard of + // the original document with the interpolation inserted. + SourceSpan SourceItpl::adjustSourceSpan(SourceSpan& pstate) const { - return SourceSpan(this); + pstate.position = + this->pstate.position + + pstate.position; + return pstate; } - ItplFile::ItplFile(const char* data, const SourceSpan& pstate) : - SourceFile(pstate.getPath(), - data, pstate.getSrcId()), - pstate(pstate) - {} - - const char* ItplFile::getRawData() const + // Account additional lines if needed. + size_t SourceItpl::countLines() { - return pstate.getRawData(); + return pstate.getSource()->countLines() + // Minus lines to replace + - pstate.span.line - 1 + // Plus lines from insert + + SourceString::countLines(); } - SourceSpan ItplFile::getSourceSpan() + // Returns source with this interpolation inserted. + // Call is quite expensive, so only use for reporting + sass::string SourceItpl::getLine(size_t line) { - return SourceSpan(pstate); + SourceData* source(pstate.getSource()); + // Calculate last line of insert + size_t lastLine = pstate.position.line - 1 + + SourceString::countLines(); + + // Calculate line difference + size_t lineDelta = 0 + // Plus lines from insert + + SourceString::countLines() + // Minus lines to replace + - pstate.span.line - 1; + + // Get full line before insert + if (line < pstate.position.line) { + return source->getLine(line); + } + // Fetch first line of insert + else if (line == pstate.position.line) { + // Get the line of around to get before part + sass::string before(source->getLine(line)); + // Check if pstate offset only spans one line + // Therefore we need to insert into the line + // Size of `2` means we have only `start` and `end` + if (lfs.size() == 2) { + // We remove some lines, need to doctor + // those together to one single line + if (pstate.span.line > 0) { + sass::string after(source->getLine( + line + pstate.span.line)); + return Unicode::replace(before, + pstate.position.column, + sass::string::npos, + SourceString::getLine(0)) + + Unicode::substr(after, + pstate.span.column, + sass::string::npos); + } + else { + // Replace in the middle + return Unicode::replace(before, + pstate.position.column, + pstate.span.column, + SourceString::getLine(0)); + } + } + else { + // Otherwise we append to substring + return Unicode::substr(before, + 0, pstate.position.column) + + SourceString::getLine(0); + } + } + // Now we must be in the inserting part + // Only happens if we have a full line + else if (line < lastLine) { + // Get full line of insert + return SourceString::getLine( + line - pstate.position.line); + } + // Fetch last line of insert + else if (line == lastLine) { + // Get line to append + sass::string after( + source->getLine( + line - lineDelta)); + // Calculate column to cut appending line + size_t col = pstate.span.line == 0 + ? pstate.position.column + pstate.span.column + : pstate.span.column; + + // Append to last line to insert + return SourceString::getLine( + line - pstate.position.line) + + Unicode::substr(after, + col, sass::string::npos); + } + else { + return source->getLine( + line - lineDelta); + } + return sass::string(); } + // EO getLine + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // Returns adjusted source span regarding interpolation (nothing to do). + SourceSpan SourceData::adjustSourceSpan(SourceSpan& pstate) const { + return pstate; + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// } diff --git a/src/source.hpp b/src/source.hpp index 77c591e250..fed456f1d9 100644 --- a/src/source.hpp +++ b/src/source.hpp @@ -1,93 +1,81 @@ -#ifndef SASS_SOURCE_H -#define SASS_SOURCE_H +#ifndef SASS_SOURCE_HPP +#define SASS_SOURCE_HPP -#include "sass.hpp" -#include "memory.hpp" -#include "position.hpp" -#include "source_data.hpp" +// sass.hpp must go before all system headers +// to get the __EXTENSIONS__ fix on Solaris. +#include "capi_sass.hpp" -namespace Sass { - - class SourceFile : - public SourceData { - protected: - char* path; - char* data; - size_t length; - size_t srcid; - public: - - SourceFile( - const char* path, - const char* data, - size_t srcid); - - ~SourceFile(); - - const char* end() const override final; - const char* begin() const override final; - virtual const char* getRawData() const override; - virtual SourceSpan getSourceSpan() override; - - size_t size() const override final { - return length; - } - - virtual const char* getPath() const override { - return path; - } +#include "ast_def_macros.hpp" - virtual size_t getSrcId() const override { - return srcid; - } +namespace Sass { - }; + class SourceSpan; - class SynthFile : - public SourceData { + // SourceData is the base class to hold loaded sass content. + class SourceData : public RefCounted + { protected: - const char* path; - public: - SynthFile( - const char* path) : - path(path) - {} + friend class SourceItpl; - ~SynthFile() {} + // Returns the number of lines. On the first call it will + // calculate the linefeed lookup table. + virtual size_t countLines() = 0; - const char* end() const override final { return nullptr; } - const char* begin() const override final { return nullptr; }; - virtual const char* getRawData() const override { return nullptr; }; - virtual SourceSpan getSourceSpan() override { return SourceSpan(path); }; + public: - size_t size() const override final { - return 0; + // Constructor + SourceData(); + + // The source id is uniquely assigned + virtual size_t getSrcIdx() const = 0; + + // The source id is uniquely assigned + virtual void setSrcIdx(size_t idx) = 0; + + // Return path as it was given for import + virtual const char* getImpPath() const = 0; + + // Return path as it was given for import + virtual const char* getAbsPath() const = 0; + + // Return only the filename part + const char* getFileName() const + { + const char* path = getImpPath(); + const char* lastSlash = path; + while (path && *path != 0) { + if (*path == '/' || *path == '\\') { + lastSlash = path + 1; + } + path += 1; + } + return lastSlash; } - virtual const char* getPath() const override { - return path; - } + // Returns the requested line. Will take interpolations into + // account to show more accurate debug messages. Calling this + // can be rather expensive, so only use it for debugging. + virtual sass::string getLine(size_t line) = 0; - virtual size_t getSrcId() const override { - return std::string::npos; - } + // Get raw iterator for raw source + virtual const char* content() const = 0; + virtual const char* srcmaps() const = 0; - }; - + // Get raw iterator for raw source + const char* contentStart() const { return content(); }; + const char* srcmapsStart() const { return srcmaps(); }; + const char* contentEnd() const { return content() + contentSize(); }; + const char* srcmapsEnd() const { return srcmaps() + srcmapsSize(); }; - class ItplFile : - public SourceFile { - private: - SourceSpan pstate; - public: + // Return raw size in bytes + virtual size_t contentSize() const = 0; + virtual size_t srcmapsSize() const = 0; - ItplFile(const char* data, - const SourceSpan& pstate); + // Returns adjusted source span regarding interpolation. + virtual SourceSpan adjustSourceSpan(SourceSpan& pstate) const; - // Offset getPosition() const override final; - const char* getRawData() const override final; - SourceSpan getSourceSpan() override final; + CAPI_WRAPPER(SourceData, SassSource); }; } diff --git a/src/source_data.hpp b/src/source_data.hpp deleted file mode 100644 index c52b02141d..0000000000 --- a/src/source_data.hpp +++ /dev/null @@ -1,32 +0,0 @@ -#ifndef SASS_SOURCE_DATA_H -#define SASS_SOURCE_DATA_H - -#include "sass.hpp" -#include "memory.hpp" - -namespace Sass { - - class SourceSpan; - - class SourceData : - public SharedObj { - public: - SourceData(); - virtual size_t size() const = 0; - virtual size_t getSrcId() const = 0; - virtual const char* end() const = 0; - virtual const char* begin() const = 0; - virtual const char* getPath() const = 0; - // virtual Offset getPosition() const = 0; - virtual const char* getRawData() const = 0; - virtual SourceSpan getSourceSpan() = 0; - - sass::string to_string() const override { - return sass::string{ begin(), end() }; - } - ~SourceData() {} - }; - -} - -#endif diff --git a/src/source_map.cpp b/src/source_map.cpp index 285d77f839..8cc6035396 100644 --- a/src/source_map.cpp +++ b/src/source_map.cpp @@ -1,202 +1,259 @@ -// sass.hpp must go before all system headers to get the -// __EXTENSIONS__ fix on Solaris. -#include "sass.hpp" - -#include -#include -#include -#include - -#include "ast.hpp" -#include "json.hpp" -#include "context.hpp" -#include "position.hpp" +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ #include "source_map.hpp" -namespace Sass { - SourceMap::SourceMap() : current_position(0, 0, 0), file("stdin") { } - SourceMap::SourceMap(const sass::string& file) : current_position(0, 0, 0), file(file) { } - - sass::string SourceMap::render_srcmap(Context &ctx) { - - const bool include_sources = ctx.c_options.source_map_contents; - const sass::vector links = ctx.srcmap_links; - const sass::vector& sources(ctx.resources); +#include "source.hpp" +#include "ast_nodes.hpp" - JsonNode* json_srcmap = json_mkobject(); +///////////////////////////////////////////////////////////////////////// +// Most UAs don't allow very detailed navigation, although LibSass +// is able to give much more detailed information about where e.g. +// certain values from a long-hand property come from. Consider the +// longhand-property `border: 1px solid red`. Currently the best UA +// seems to be anything WebKit based (like Chrome). There you can +// actually reach the source (by clicking on it while holding the +// ctrl key) for `border` and `1px solid red`. We could actually +// give the source position for each value, e.g. `1px` or `red` +// individually, but currently no UA seems to supports this. Same +// applies for complex selector, where we could give the source for +// every compound selector (basically for every word token). +///////////////////////////////////////////////////////////////////////// +// Implementation of Source-Maps in UAs also have another flaw (IMO). +// It seems UAs work actively against us from embedding more detailed +// information, which could be interesting for re-mapping operations, +// e.g. when files flow through multiple processors. Consider the +// selector `foo bar baz`. Since UAs can at best only navigate to one +// certain source position, we have to decide where this should be. +// Naturally we probably want it to point the most inner block. To +// illustrate consider the following sass code (with marked position): +// `[A]foo { [B]bar { [C]baz { ... }}}`. Rendered with source positions +// this might look like this: `[A]foo [B]bar [C]baz { ... }`. In case +// we render the results like this, UAs will link the final selector +// to the most outer block [A], which is quite useless for Sass files, +// since you will probably have one big block there. Now you might +// think we could just fiddle with it a little to make it look like +// `[C][A]foo [B]bar [C]baz`. Unfortunately this doesn't improve +// the situation, as the only way this will work correctly is +// `[A][C]foo [B]bar [C]baz`. Although not unsolvable, it really +// is pretty `out of order` and needs some dirty flags to work +// around in the code-base Well, it is what it is :-/ +///////////////////////////////////////////////////////////////////////// +// Since I couldn't decide how to proceed, I decided to try to +// offer all possible direction one could like to take this. Not +// yet sure if we will ever expose this directly on the C-API side. +// I basically see a few different desirable cases: +// - Minimum payload to just make it work in UAs +// - More detailed version including crutch to fix UAs +// - Fully detailed version without crutch included +///////////////////////////////////////////////////////////////////////// +namespace Sass { - json_append_member(json_srcmap, "version", json_mknumber(3)); + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// - const char *file_name = file.c_str(); - JsonNode *json_file_name = json_mkstring(file_name); - json_append_member(json_srcmap, "file", json_file_name); + // Empty constructor + SourceMap::SourceMap() : + position() + {} - // pass-through sourceRoot option - if (!ctx.source_map_root.empty()) { - JsonNode* root = json_mkstring(ctx.source_map_root.c_str()); - json_append_member(json_srcmap, "sourceRoot", root); - } + // Call when text in the original was appended + void SourceMap::append(const Offset& offset) + { + position += offset; + } + // EO append - JsonNode *json_sources = json_mkarray(); - for (size_t i = 0; i < source_index.size(); ++i) { - sass::string source(links[source_index[i]]); - if (ctx.c_options.source_map_file_urls) { - source = File::rel2abs(source); - // check for windows abs path - if (source[0] == '/') { - // ends up with three slashes - source = "file://" + source; - } else { - // needs an additional slash - source = "file:///" + source; + // Call when text in the original was prepended + void SourceMap::prepend(const Offset& offset) + { + if (offset.line != 0 || offset.column != 0) { + for (Mapping& mapping : mappings) { + // move stuff on the first old line + if (mapping.target.line == 0) { + mapping.target.column += offset.column; } + // make place for the new lines + mapping.target.line += offset.line; } - const char* source_name = source.c_str(); - JsonNode *json_source_name = json_mkstring(source_name); - json_append_element(json_sources, json_source_name); } - json_append_member(json_srcmap, "sources", json_sources); - - if (include_sources && source_index.size()) { - JsonNode *json_contents = json_mkarray(); - for (size_t i = 0; i < source_index.size(); ++i) { - const Resource& resource(sources[source_index[i]]); - JsonNode *json_content = json_mkstring(resource.contents); - json_append_element(json_contents, json_content); - } - json_append_member(json_srcmap, "sourcesContent", json_contents); + if (position.line == 0) { + position.column += offset.column; } - - JsonNode *json_names = json_mkarray(); - // so far we have no implementation for names - // no problem as we do not alter any identifiers - json_append_member(json_srcmap, "names", json_names); - - sass::string mappings = serialize_mappings(); - JsonNode *json_mappings = json_mkstring(mappings.c_str()); - json_append_member(json_srcmap, "mappings", json_mappings); - - char *str = json_stringify(json_srcmap, "\t"); - sass::string result = sass::string(str); - free(str); - json_delete(json_srcmap); - return result; + position.line += offset.line; } + // EO prepend - sass::string SourceMap::serialize_mappings() { - sass::string result = ""; - - size_t previous_generated_line = 0; - size_t previous_generated_column = 0; - size_t previous_original_line = 0; - size_t previous_original_column = 0; - size_t previous_original_file = 0; - for (size_t i = 0; i < mappings.size(); ++i) { - const size_t generated_line = mappings[i].generated_position.line; - const size_t generated_column = mappings[i].generated_position.column; - const size_t original_line = mappings[i].original_position.line; - const size_t original_column = mappings[i].original_position.column; - const size_t original_file = mappings[i].original_position.file; - - if (generated_line != previous_generated_line) { - previous_generated_column = 0; - if (generated_line > previous_generated_line) { - result += sass::string(generated_line - previous_generated_line, ';'); - previous_generated_line = generated_line; - } - } - else if (i > 0) { - result += ","; - } - - // generated column - result += base64vlq.encode(static_cast(generated_column) - static_cast(previous_generated_column)); - previous_generated_column = generated_column; - // file - result += base64vlq.encode(static_cast(original_file) - static_cast(previous_original_file)); - previous_original_file = original_file; - // source line - result += base64vlq.encode(static_cast(original_line) - static_cast(previous_original_line)); - previous_original_line = original_line; - // source column - result += base64vlq.encode(static_cast(original_column) - static_cast(previous_original_column)); - previous_original_column = original_column; - } - - return result; + // Call when another buffer is appended to the original + void SourceMap::append(const OutputBuffer& out) + { + append(Offset(out.buffer)); } + // EO append + // Call when another buffer is prepended to the original void SourceMap::prepend(const OutputBuffer& out) { - Offset size(out.smap.current_position); - for (Mapping mapping : out.smap.mappings) { - if (mapping.generated_position.line > size.line) { - throw(std::runtime_error("prepend sourcemap has illegal line")); - } - if (mapping.generated_position.line == size.line) { - if (mapping.generated_position.column > size.column) { - throw(std::runtime_error("prepend sourcemap has illegal column")); + if (out.srcmap) { + Offset size(out.srcmap->position); + for (const Mapping& mapping : out.srcmap->mappings) { + if (mapping.target.line > size.line) { + throw(std::runtime_error("prepend sourcemap has illegal line")); + } + if (mapping.target.line == size.line) { + if (mapping.target.column > size.column) { + throw(std::runtime_error("prepend sourcemap has illegal column")); + } } } + // adjust the buffer offset + prepend(Offset(out.buffer)); + // now add the new mappings + mappings.insert(mappings.begin(), + out.srcmap->mappings.begin(), + out.srcmap->mappings.end()); } - // adjust the buffer offset - prepend(Offset(out.buffer)); - // now add the new mappings - VECTOR_UNSHIFT(mappings, out.smap.mappings); } + // EO prepend - void SourceMap::append(const OutputBuffer& out) + // Add mapping pointing to ast node end position + void SourceMap::moveNextMapping(int start, int end) { - append(Offset(out.buffer)); + moveNextSrc = start; + moveNextDst = end; } - void SourceMap::prepend(const Offset& offset) + // Add mapping pointing to ast node start position + void SourceMap::addOpenMapping(const AstNode* node, bool optional) { - if (offset.line != 0 || offset.column != 0) { - for (Mapping& mapping : mappings) { - // move stuff on the first old line - if (mapping.generated_position.line == 0) { - mapping.generated_position.column += offset.column; - } - // make place for the new lines - mapping.generated_position.line += offset.line; - } + if (optional && !useOptionalOpeners) { + moveNextSrc = 0; + moveNextDst = 0; + return; } - if (current_position.line == 0) { - current_position.column += offset.column; + const SourceSpan& pstate = node->pstate(); + if (pstate.getSrcIdx() != sass::string::npos) { + auto source_start = pstate.position; + if (moveNextSrc || moveNextDst) { + source_start.column += moveNextSrc; + position.column += moveNextDst; + } + mappings.emplace_back(Mapping{ + pstate.getSrcIdx(), + source_start, + position + }); + if (moveNextSrc || moveNextDst) { + source_start.column -= moveNextSrc; + position.column -= moveNextDst; + moveNextSrc = 0; + moveNextDst = 0; + } } - current_position.line += offset.line; } + // EO addOpenMapping - void SourceMap::append(const Offset& offset) + // Add mapping pointing to ast node end position + void SourceMap::addCloseMapping(const AstNode* node, bool optional) { - current_position += offset; + if (optional && !useOptionalClosers) { + moveNextSrc = 0; + moveNextDst = 0; + return; + } + const SourceSpan& pstate = node->pstate(); + if (pstate.getSrcIdx() != sass::string::npos) { + auto source_end = pstate.position + pstate.span; + if (moveNextSrc || moveNextDst) { + source_end.column += moveNextSrc; + position.column += moveNextDst; + } + mappings.emplace_back(Mapping{ + pstate.getSrcIdx(), + source_end, + position + }); + if (moveNextSrc || moveNextDst) { + source_end.column -= moveNextSrc; + position.column -= moveNextDst; + moveNextSrc = 0; + moveNextDst = 0; + } + } } + // EO addCloseMapping - void SourceMap::add_open_mapping(const AST_Node* node) + sass::string SourceMap::render(const std::unordered_map& idxremap) const { - const SourceSpan& span(node->pstate()); - Position from(span.getSrcId(), span.position); - mappings.push_back(Mapping(from, current_position)); - } - void SourceMap::add_close_mapping(const AST_Node* node) - { - const SourceSpan& span(node->pstate()); - Position to(span.getSrcId(), span.position + span.offset); - mappings.push_back(Mapping(to, current_position)); - } + sass::string result; + + // Object for encoding state + Base64VLQ base64vlq; + + // We can make an educated guess here + // 3249594 mappings = 17669768 bytes + result.reserve(mappings.size() * 5); + + int previous_generated_line = 0; + int previous_generated_column = 0; + int previous_original_line = 0; + int previous_original_column = 0; + int previous_original_file = 0; - SourceSpan SourceMap::remap(const SourceSpan& pstate) { for (size_t i = 0; i < mappings.size(); ++i) { - if ( - mappings[i].generated_position.file == pstate.getSrcId() && - mappings[i].generated_position.line == pstate.position.line && - mappings[i].generated_position.column == pstate.position.column - ) return SourceSpan(pstate.source, mappings[i].original_position, pstate.offset); + int generated_line = static_cast(mappings[i].target.line); + int generated_column = static_cast(mappings[i].target.column); + int original_line = static_cast(mappings[i].origin.line); + int original_column = static_cast(mappings[i].origin.column); + int original_file = static_cast(idxremap.at(mappings[i].srcidx)); + bool linefeed = generated_line != previous_generated_line; + + if (linefeed) { + previous_generated_column = 0; + if (generated_line > previous_generated_line) { + result += sass::string(size_t(generated_line) - previous_generated_line, ';'); + previous_generated_line = generated_line; + } + } + + auto generated_offset = generated_column - previous_generated_column; + auto file_delta = original_file - previous_original_file; + auto line_delta = original_line - previous_original_line; + auto col_delta = original_column - previous_original_column; + + // maybe we can optimize this a bit in the future? + // Only emit mappings if it is actually pointing at something new + if (!i || generated_offset || file_delta || line_delta || col_delta) { + if (!linefeed && i) result += ','; + base64vlq.encode(result, generated_offset); + base64vlq.encode(result, file_delta); + base64vlq.encode(result, line_delta); + base64vlq.encode(result, col_delta); + } + + previous_generated_column = generated_column; + previous_original_column = original_column; + previous_original_line = original_line; + previous_original_file = original_file; } - return SourceSpan(pstate.source, Position(-1, -1, -1), Offset(0, 0)); + return result; } + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + OutputBuffer::OutputBuffer(bool srcmap) noexcept : + srcmap(srcmap ? new SourceMap() : nullptr) + {} + + OutputBuffer::OutputBuffer(OutputBuffer&& old) noexcept : + buffer(std::move(old.buffer)), + srcmap(std::move(old.srcmap)) + {} + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + } diff --git a/src/source_map.hpp b/src/source_map.hpp index 6472fd2a60..fef040ea84 100644 --- a/src/source_map.hpp +++ b/src/source_map.hpp @@ -1,63 +1,121 @@ -#ifndef SASS_SOURCE_MAP_H -#define SASS_SOURCE_MAP_H +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#ifndef SASS_SOURCE_MAP_HPP +#define SASS_SOURCE_MAP_HPP -#include -#include +// sass.hpp must go before all system headers +// to get the __EXTENSIONS__ fix on Solaris. +#include "capi_sass.hpp" #include "ast_fwd_decl.hpp" +#include "source_span.hpp" +#include "backtrace.hpp" #include "base64vlq.hpp" -#include "position.hpp" #include "mapping.hpp" -#include "backtrace.hpp" -#include "memory.hpp" - -#define VECTOR_PUSH(vec, ins) vec.insert(vec.end(), ins.begin(), ins.end()) -#define VECTOR_UNSHIFT(vec, ins) vec.insert(vec.begin(), ins.begin(), ins.end()) - namespace Sass { - class Context; - class OutputBuffer; + ///////////////////////////////////////////////////////////////////////// + // Helper class to create the source-mappings. + ///////////////////////////////////////////////////////////////////////// class SourceMap { + private: + + // Sources by index + sass::vector sources; + + // List of all source mappings + // Deque is not faster, I checked + sass::vector mappings; + + // Current position + Offset position; + + // Flags to move column position for next mapping a little + // Can be useful to e.g. skip over a leading non-word character. + int moveNextSrc = 0; + int moveNextDst = 0; + + // Options to include more or less details + bool useOptionalOpeners = false; + bool useOptionalClosers = false; + public: - sass::vector source_index; + + // Empty constructor SourceMap(); - SourceMap(const sass::string& file); + // Reserve space beforehand + void reserve(size_t size) { + sources.reserve(size); + mappings.reserve(size); + } + + // Register a new source index + void addSourceIndex(size_t idx) { + sources.push_back(idx); + } + + // Call when text in the original was appended void append(const Offset& offset); + + // Call when text in the original was prepended void prepend(const Offset& offset); + + // Call when another buffer is appended to the original void append(const OutputBuffer& out); + + // Call when another buffer is prepended to the original void prepend(const OutputBuffer& out); - void add_open_mapping(const AST_Node* node); - void add_close_mapping(const AST_Node* node); - sass::string render_srcmap(Context &ctx); - SourceSpan remap(const SourceSpan& pstate); + // Add mapping pointing to ast node start position + void addOpenMapping(const AstNode* node, bool optional); - private: + // Add mapping pointing to ast node end position + void addCloseMapping(const AstNode* node, bool optional); - sass::string serialize_mappings(); + // Set flags to move column position for next mapping a little + // Can be useful to e.g. skip over a leading non-word character. + void moveNextMapping(int start, int end = 0); + + // Setter to enable/disable additional more detailed source mappings + void enableOptionalOpeners(bool enable) { useOptionalOpeners = enable; } + void enableOptionalClosers(bool enable) { useOptionalClosers = enable; } + + // Render the source-map into comma-separated base64 encoded representation + sass::string render(const std::unordered_map& remap_srcidx) const; - sass::vector mappings; - Position current_position; -public: - sass::string file; -private: - Base64VLQ base64vlq; }; + ///////////////////////////////////////////////////////////////////////// + // Helper class to hold output with srcmap attached. + ///////////////////////////////////////////////////////////////////////// + class OutputBuffer { - public: - OutputBuffer(void) - : buffer(), - smap() - { } - public: - sass::string buffer; - SourceMap smap; + + private: + + // Make sure we don't allow any copies + OutputBuffer(const OutputBuffer&) = delete; + OutputBuffer& operator=(const OutputBuffer&) = delete; + + public: + + // Main buffer string + sass::string buffer; + + // Optional source map + SourceMap* srcmap; + + // Default constructor + OutputBuffer(bool srcmap = false) noexcept; + + // Allow to move the buffer + OutputBuffer(OutputBuffer&&) noexcept; + }; } diff --git a/src/source_span.cpp b/src/source_span.cpp new file mode 100644 index 0000000000..6ae72d15f3 --- /dev/null +++ b/src/source_span.cpp @@ -0,0 +1,51 @@ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#include "source_span.hpp" + +#include "sources.hpp" +#include "ast_nodes.hpp" + +namespace Sass { + + // Regular value constructor + SourceSpan::SourceSpan( + SourceDataObj source, + const Offset& position, + const Offset& span) : + SourceState(source, position), + span(span) + {} + + // Create SourceSpan for internal things + SourceSpan SourceSpan::internal(const char* label) + { + return SourceSpan(SASS_MEMORY_NEW( + SourceString, "sass://internal", label), + Offset{}, Offset{}); + } + + // Create span between `lhs.start` and `rhs.end` (must be same source) + SourceSpan SourceSpan::delta(const SourceSpan& lhs, const SourceSpan& rhs) + { + return SourceSpan( + lhs.getSource(), lhs.position, + Offset::distance(lhs.position, + rhs.position + rhs.span)); + } + + // Create span between two ast-node source-spans + SourceSpan SourceSpan::delta(AstNode* lhs, AstNode* rhs) + { + return SourceSpan::delta( + lhs->pstate(), rhs->pstate()); + } + + bool SourceSpan::operator==(const SourceSpan& rhs) const + { + return source.ptr() == rhs.source.ptr() + && position == rhs.position + && span == rhs.span; + } + +} diff --git a/src/source_span.hpp b/src/source_span.hpp new file mode 100644 index 0000000000..e2a0b6dbdb --- /dev/null +++ b/src/source_span.hpp @@ -0,0 +1,51 @@ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#ifndef SASS_SOURCE_SPAN_HPP +#define SASS_SOURCE_SPAN_HPP + +// sass.hpp must go before all system headers +// to get the __EXTENSIONS__ fix on Solaris. +#include "capi_sass.hpp" + +#include "source_state.hpp" + +namespace Sass { + + // ParseState is now SourceSpan + class SourceSpan : public SourceState + { + + public: + + // Offset size + Offset span; + + // Empty constructor + SourceSpan() {} + + // Regular value constructor + SourceSpan(SourceDataObj source, + const Offset& position = Offset(), + const Offset& span = Offset()); + + // Create SourceSpan for internal things + static SourceSpan internal(const char* path); + + // Create span between `lhs.start` and `rhs.end` (must be same source) + static SourceSpan delta(const SourceSpan& lhs, const SourceSpan& rhs); + + // Create span between two ast-node source-spans + static SourceSpan delta(AstNode* lhs, AstNode* rhs); + + bool operator==(const SourceSpan& rhs) const; + + public: // down casts + + CAPI_WRAPPER(SourceSpan, SassSrcSpan); + + }; + +} // namespace Sass + +#endif diff --git a/src/source_state.cpp b/src/source_state.cpp new file mode 100644 index 0000000000..2cbf03e278 --- /dev/null +++ b/src/source_state.cpp @@ -0,0 +1,71 @@ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#include "source_state.hpp" + +#include "source.hpp" +#include "file.hpp" + +namespace Sass +{ + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // Regular value constructor + SourceState::SourceState( + SourceData* source, + Offset position) : + source(source), + position(position) + {} + + // Return the attach source id + size_t SourceState::getSrcIdx() const + { + return source->getSrcIdx(); + } + + // Return the requested import path + const char* SourceState::getImpPath() const + { + return source->getImpPath(); + } + + // Return the resolved absolute path + const char* SourceState::getAbsPath() const + { + return source->getAbsPath(); + } + + // Return the resolved absolute path + const char* SourceState::getFileName() const + { + return source->getFileName(); + } + + // Return the attached source + SourceData* SourceState::getSource() const + { + return source.ptr(); + } + + // Return the attached source + const char* SourceState::getContent() const + { + return source->content(); + } + + // Either return path relative to cwd if path is + // inside cwd, otherwise return absolute path. + sass::string SourceState::getDebugPath() const + { + const char* path = getAbsPath(); + sass::string rel_path(File::abs2rel(path, CWD(), CWD())); + return rel_path.substr(0, 3) == "../" ? path : rel_path; + } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + +} diff --git a/src/source_state.hpp b/src/source_state.hpp new file mode 100644 index 0000000000..1e8f13a290 --- /dev/null +++ b/src/source_state.hpp @@ -0,0 +1,77 @@ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#ifndef SASS_SOURCE_STATE_HPP +#define SASS_SOURCE_STATE_HPP + +// sass.hpp must go before all system headers +// to get the __EXTENSIONS__ fix on Solaris. +#include "capi_sass.hpp" + +#include "ast_fwd_decl.hpp" +#include "offset.hpp" +#include "source.hpp" + +namespace Sass +{ + + // Stores a reference (shared ptr) to the source code + // and one offset position (line and column information). + class SourceState + { + protected: + + // The source code reference + SourceDataObj source; + + public: + + // The position within the source + Offset position; + + // Regular value constructor + SourceState( + SourceData* source = {}, + Offset position = Offset()); + + // Return the attach source id + size_t getSrcIdx() const; + + // Return the requested import path + const char* getImpPath() const; + + // Return the resolved absolute path + const char* getAbsPath() const; + + // Return the resolved filename + const char* getFileName() const; + + // Return the attached source + SourceData* getSource() const; + + // Return the attached source + const char* getContent() const; + + // Return line as human readable + // Starting from one instead of zero + uint32_t getLine() const + { + return position.line + 1; + } + + // Return line as human readable + // Starting from one instead of zero + uint32_t getColumn() const + { + return position.column + 1; + } + + // Either return path relative to cwd if path is + // inside cwd, otherwise return absolute path. + sass::string getDebugPath() const; + + }; + +} + +#endif diff --git a/src/sources.hpp b/src/sources.hpp new file mode 100644 index 0000000000..979723ff4d --- /dev/null +++ b/src/sources.hpp @@ -0,0 +1,236 @@ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#ifndef SASS_SOURCES_HPP +#define SASS_SOURCES_HPP + +// sass.hpp must go before all system headers +// to get the __EXTENSIONS__ fix on Solaris. +#include "capi_sass.hpp" + +#include "source.hpp" +#include "source_span.hpp" + +namespace Sass { + + ///////////////////////////////////////////////////////////////////////// + // Base class for our two main implementations. + // The main API is `const char*` based. + ///////////////////////////////////////////////////////////////////////// + class SourceWithPath : public SourceData + { + protected: + + // Import path + sass::string imp_path; + + // Resolved path + sass::string abs_path; + + // Raw length in bytes + size_t len_content; + size_t len_srcmaps; + + // Unique source id + size_t srcidx; + + // Store byte offset for every line. + // Lazy calculated within `countLines`. + // Columns per line can be derived from it. + sass::vector lfs; + + // Returns the number of lines. On first call + // it will calculate the linefeed lookup table. + virtual size_t countLines() override; + + public: + + // Value move constructor + SourceWithPath( + sass::string&& imp_path, + sass::string&& abs_path, + size_t idx = sass::string::npos); + + // Value copy constructor + SourceWithPath( + const sass::string& imp_path, + const sass::string& abs_path, + size_t idx = sass::string::npos); + + // Returns the requested line. Will take interpolations into + // account to show more accurate debug messages. Calling this + // can be rather expensive, so only use it for debugging. + virtual sass::string getLine(size_t line) override; + + // Return path as it was given for import + const char* getImpPath() const override final + { + return imp_path.empty() ? + nullptr : imp_path.c_str(); + } + + // Return path after it was resolved + const char* getAbsPath() const override final + { + return abs_path.empty() ? + nullptr : abs_path.c_str(); + } + + // The source id is uniquely assigned + void setSrcIdx(size_t idx) override final + { + srcidx = idx; + } + + // The source id is uniquely assigned + size_t getSrcIdx() const override final + { + return srcidx; + } + + size_t contentSize() const override final + { + return len_content; + } + + size_t srcmapsSize() const override final + { + return len_srcmaps; + } + + }; + + ///////////////////////////////////////////////////////////////////////// + // A SourceFile is meant to be used for externally loaded resource. + // The resources passed in will be taken over and disposed at the end. + // Resources must have been allocated via `sass_alloc_memory`. + ///////////////////////////////////////////////////////////////////////// + class SourceFile final : public SourceWithPath + { + protected: + + // Raw source data + char* _content; + + // Raw source data + char* _srcmaps; + + public: + + // Value copy/move constructor + // Copied: imp_path and abs_path + // Moved: content and srcmaps data + SourceFile( + const char* imp_path, // copy + const char* abs_path, // copy + char* content, // take ownership + char* srcmaps, // take ownership + size_t srcidx = sass::string::npos); + + // Destructor + ~SourceFile() override final; + + // Get raw iterator for actual source + const char* content() const override final { + return _content; + } + + // Get raw iterator for actual source + const char* srcmaps() const override final { + return _srcmaps; + } + + }; + + ///////////////////////////////////////////////////////////////////////// + // A SourceString is meant to be used internally when we need to + // re-parse evaluated interpolations or static function signatures. + ///////////////////////////////////////////////////////////////////////// + class SourceString : + public SourceWithPath { + + protected: + + // Raw source data + sass::string _content; + + // Raw source data + sass::string _srcmaps; + + public: + + // Value move constructor without srcmaps + // ToDo: should we try to parse srcmaps? + SourceString( + const char* path, + sass::string&& data); + + // Value move constructor with srcmaps + SourceString( + const char* imp_path, + const char* abs_path, + sass::string&& data, + sass::string&& srcmap, + size_t srcidx = sass::string::npos); + + // Get raw iterator for actual source + const char* content() const override final { + return _content.c_str(); + } + + // Get raw iterator for actual source + const char* srcmaps() const override final { + return _srcmaps.c_str(); + } + + }; + + ///////////////////////////////////////////////////////////////////////// + // This class helps to report more meaningful errors when interpolations + // are involved. We basically replace the original interpolation with the + // result after evaluation. We can also adjust your parser state, since we + // often only re-parse the partial interpolated object (e.g. selector in + // the middle of a document). The error will be relative to this snippet. + // E.g. on line 1, after adjusting it should be in sync with whatever the + // `getLine` API returns. We do all this only on demand, since this is quite + // expensive, so this is only intended to be used in error/debug cases!! + ///////////////////////////////////////////////////////////////////////// + class SourceItpl : + public SourceString { + + protected: + + // Account additional lines if needed. + size_t countLines() override final; + + private: + + // The position where the interpolation occurred. + // We also get the parent source from this state. + // Plus the parent `SourceString` we have it all. + SourceSpan pstate; + + public: + + // Create a synthetic interpolated source. The `data` is the + // evaluated interpolation, while `around` is the original source + // where the actual interpolation was given at `pstate` position. + SourceItpl(SourceSpan pstate, sass::string&& data); + + // Returns source with this interpolation inserted. + sass::string getLine(size_t line) override final; + + // Returns adjusted source span with interpolation in mind. + // The input `pstate` is relative to the interpolation, will + // return a source span with absolute position in regard of + // the original document with the interpolation inserted. + SourceSpan adjustSourceSpan(SourceSpan& pstate) const override final; + + }; + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + +} + +#endif diff --git a/src/string_utils.cpp b/src/string_utils.cpp new file mode 100644 index 0000000000..c0fe1c9fb9 --- /dev/null +++ b/src/string_utils.cpp @@ -0,0 +1,236 @@ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#include "string_utils.hpp" + +#include +#include + +namespace Sass { + + namespace StringUtils { + + using namespace Character; + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + bool _equalsIgnoreCase(const char a, const char b) { + return Character::characterEqualsIgnoreCase(a, b); + } + + // Optimized version where we know one side is already lowercase + bool _equalsIgnoreCaseConst(const char a, const char b) { + return a == b || a == Character::toLowerCase(b); + } + + bool startsWith(const sass::string& str, const char* prefix, size_t len) { + return len <= str.size() && std::equal(prefix, prefix + len, str.begin()); + } + + bool startsWith(const sass::string& str, const sass::string& prefix) { + return prefix.size() <= str.size() && std::equal(prefix.begin(), prefix.end(), str.begin()); + } + + bool endsWith(const sass::string& str, const char* suffix, size_t len) { + return len <= str.size() && std::equal(suffix, suffix + len, str.end() - len); + } + + bool endsWith(const sass::string& str, const sass::string& suffix) { + return suffix.size() <= str.size() && std::equal(suffix.rbegin(), suffix.rend(), str.rbegin()); + } + + // Optimized version when you pass `const char*` you know is already lowercase. + bool startsWithIgnoreCase(const sass::string& str, const char* prefix, size_t len) { + return len <= str.size() && std::equal(prefix, prefix + len, str.begin(), _equalsIgnoreCaseConst); + } + + bool startsWithIgnoreCase(const sass::string& str, const sass::string& prefix) { + return prefix.size() <= str.size() && std::equal(prefix.begin(), prefix.end(), str.begin(), _equalsIgnoreCase); + } + + // Optimized version when you pass `const char*` you know is already lowercase. + bool endsWithIgnoreCase(const sass::string& str, const char* suffix, size_t len) { + return len <= str.size() && std::equal(suffix, suffix + len, str.end() - len, _equalsIgnoreCaseConst); + } + + bool endsWithIgnoreCase(const sass::string& str, const sass::string& suffix) { + return suffix.size() <= str.size() && std::equal(suffix.rbegin(), suffix.rend(), str.rbegin(), _equalsIgnoreCase); + } + + bool equalsIgnoreCase(const sass::string& a, const char* b, size_t len) { + return len == a.size() && std::equal(b, b + len, a.begin(), _equalsIgnoreCaseConst); + } + + bool equalsIgnoreCase(const sass::string& a, const sass::string& b) { + return a.size() == b.size() && std::equal(b.begin(), b.end(), a.begin(), _equalsIgnoreCaseConst); + } + + // Make the passed string whitespace trimmed. + void makeTrimmed(sass::string& str) { + makeLeftTrimmed(str); + makeRightTrimmed(str); + } + // EO makeTrimmed + + // Trim the left side of passed string. + void makeLeftTrimmed(sass::string& str) { + if (str.begin() != str.end()) { + auto pos = std::find_if_not( + str.begin(), str.end(), + Character::isWhitespace); + str.erase(str.begin(), pos); + } + } + // EO makeLeftTrimmed + + // Trim the right side of passed string. + void makeRightTrimmed(sass::string& str) { + if (str.begin() != str.end()) { + auto pos = std::find_if_not( + str.rbegin(), str.rend(), + Character::isWhitespace); + str.erase(str.rend() - pos); + } + } + // EO makeRightTrimmed + + // Make the passed string lowercase. + void makeLowerCase(sass::string& str) { + for (char& character : str) { + if (character >= $A && character <= $Z) + character |= asciiCaseBit; + } + } + // EO makeLowerCase + + // Make the passed string uppercase. + void makeUpperCase(sass::string& str) { + for (char& character : str) { + if (character >= $a && character <= $z) + character &= ~asciiCaseBit; + } + } + // EO makeUpperCase + + // Return new string converted to lowercase. + sass::string toLowerCase(const sass::string& str) { + sass::string rv(str); + for (char& character : rv) { + if (character >= $A && character <= $Z) + character |= asciiCaseBit; + } + return rv; + } + // EO toLowerCase + + // Return new string converted to uppercase. + sass::string toUpperCase(const sass::string& str) { + sass::string rv(str); + for (char& character : rv) { + if (character >= $a && character <= $z) + character &= ~asciiCaseBit; + } + return rv; + } + // EO toUpperCase + + + // Check if string contains white-space only + // Returns true if string to check is empty + bool isWhitespaceOnly(const sass::string& str) + { + for (const char& character : str) { + if (!isWhitespace(character)) + return false; + } + return true; + } + // EO isWhitespaceOnly + + // Replace all occurrences of `search` in string `str` with `replacement`. + void makeReplace(sass::string& str, const sass::string& search, const sass::string& replacement) + { + size_t pos = str.find(search); + while (pos != std::string::npos) + { + str.replace(pos, search.size(), replacement); + pos = str.find(search, pos + replacement.size()); + } + } + // EO makeReplace + + // Return list of strings split by `delimiter`. + // Optionally `trim` all results (default behavior). + sass::vector split(sass::string str, char delimiter, bool trim) + { + sass::vector rv; + if (trim) StringUtils::makeTrimmed(str); + if (str.empty()) return rv; + size_t start = 0, end = str.find_first_of(delimiter); + // search until we are at the end + while (end != sass::string::npos) { + // add substring from current position to delimiter + sass::string item(str.substr(start, end - start)); + if (trim) StringUtils::makeTrimmed(item); + if (!trim || !item.empty()) rv.emplace_back(item); + start = end + 1; // skip delimiter + end = str.find_first_of(delimiter, start); + } + // add last substring from current position to end + sass::string item(str.substr(start, end - start)); + if (trim) StringUtils::makeTrimmed(item); + if (!trim || !item.empty()) rv.emplace_back(item); + // return back + return rv; + } + // EO split + + // Return joined string from all passed strings, delimited by separator. + sass::string join(const sass::vector& strings, const char* separator) + { + switch (strings.size()) + { + case 0: + return ""; + case 1: + return strings[0]; + default: + size_t size = strings[0].size(); + size_t sep_len = ::strlen(separator); + for (size_t i = 1; i < strings.size(); i++) { + size += sep_len + strings[i].size(); + } + sass::string os; + os.reserve(size); + os += strings[0]; + for (size_t i = 1; i < strings.size(); i++) { + os += separator; + os += strings[i]; + } + return os; + } + } + // EO join + + ///////////////////////////////////////////////////////////////////////// + // Returns [name] without a vendor prefix. + // If [name] has no vendor prefix, it's returned as-is. + ///////////////////////////////////////////////////////////////////////// + sass::string unvendor(const sass::string& name) + { + if (name.size() < 2) return name; + 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 + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + } +} diff --git a/src/string_utils.hpp b/src/string_utils.hpp new file mode 100644 index 0000000000..3b29ca9112 --- /dev/null +++ b/src/string_utils.hpp @@ -0,0 +1,72 @@ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#ifndef SASS_STRING_UTILS_HPP +#define SASS_STRING_UTILS_HPP + +// sass.hpp must go before all system headers +// to get the __EXTENSIONS__ fix on Solaris. +#include "capi_sass.hpp" + +#include "character.hpp" + +namespace Sass { + namespace StringUtils { + + // Standard string utility functions (names are self-explanatory) + bool endsWith(const sass::string& str, const sass::string& suffix); + bool startsWith(const sass::string& str, const sass::string& prefix); + bool equalsIgnoreCase(const sass::string& a, const sass::string& b); + bool endsWithIgnoreCase(const sass::string& str, const sass::string& suffix); + bool startsWithIgnoreCase(const sass::string& str, const sass::string& prefix); + + // Optimized versions when you pass `const char*` you know is already lowercase. + bool endsWith(const sass::string& str, const char* suffix, size_t len); + bool startsWith(const sass::string& str, const char* prefix, size_t len); + bool equalsIgnoreCase(const sass::string& a, const char* b, size_t len); + bool endsWithIgnoreCase(const sass::string& str, const char* suffix, size_t len); + bool startsWithIgnoreCase(const sass::string& str, const char* prefix, size_t len); + + // Make the passed string whitespace trimmed. + void makeTrimmed(sass::string& str); + + // Trim the left side of passed string. + void makeLeftTrimmed(sass::string& str); + + // Trim the right side of passed string. + void makeRightTrimmed(sass::string& str); + + // Make the passed string lowercase. + void makeLowerCase(sass::string& str); + + // Make the passed string uppercase. + void makeUpperCase(sass::string& str); + + // Return new string converted to lowercase. + sass::string toUpperCase(const sass::string& str); + + // Return new string converted to uppercase. + sass::string toLowerCase(const sass::string& str); + + // Check if string contains white-space only + // Returns true if string to check is empty + bool isWhitespaceOnly(const sass::string& str); + + // Replace all occurrences of `search` in string `str` with `replacement`. + void makeReplace(sass::string& str, const sass::string& search, const sass::string& replacement); + + // Return list of strings split by `delimiter`. + // Optionally `trim` all results (default behavior). + sass::vector split(sass::string str, char delimiter, bool trim = true); + + // Return joined string from all passed strings, delimited by separator. + sass::string join(const sass::vector& strings, const char* separator); + + // Returns [name] without a vendor prefix. + // If [name] has no vendor prefix, it's returned as-is. + sass::string unvendor(const sass::string& name); + + } +} + +#endif diff --git a/src/strings.cpp b/src/strings.cpp new file mode 100644 index 0000000000..988df1ae0b --- /dev/null +++ b/src/strings.cpp @@ -0,0 +1,525 @@ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#include "strings.hpp" + +namespace Sass +{ + + const sass::string str_empty(""); + + // For list functions + const sass::string str_length("length"); + const sass::string str_nth("nth"); + const sass::string str_set_nth("set-nth"); + const sass::string str_join("join"); + const sass::string str_append("append"); + const sass::string str_zip("zip"); + const sass::string str_slash("slash"); + const sass::string str_list_separator("list-separator"); + const sass::string str_is_bracketed("is-bracketed"); + + // Rounding strategies + const sass::string str_up = "up"; + const sass::string str_down = "down"; + const sass::string str_nearest = "nearest"; + const sass::string str_to_zero = "to-zero"; + + // For map functions + const sass::string str_set("set"); + const sass::string str_map_set("map-set"); + const sass::string str_get("get"); + const sass::string str_map_get("map-get"); + const sass::string str_merge("merge"); + const sass::string str_map_merge("map-merge"); + const sass::string str_remove("remove"); + const sass::string str_map_remove("map-remove"); + const sass::string str_keys("keys"); + const sass::string str_map_keys("map-keys"); + const sass::string str_base("base"); + const sass::string str_deg("deg"); + const sass::string str_angle("angle"); + const sass::string str_number("number"); + const sass::string str_value("value"); + const sass::string str_values("values"); + const sass::string str_map_values("map-values"); + const sass::string str_has_key("has-key"); + const sass::string str_map_has_key("map-has-key"); + const sass::string str_deep_merge("deep-merge"); + const sass::string str_deep_remove("deep-remove"); + + // For text functions + const sass::string str_unquote("unquote"); + const sass::string str_quote("quote"); + const sass::string str_to_upper_case("to-upper-case"); + const sass::string str_to_lower_case("to-lower-case"); + // const sass::string str_length("length"); + const sass::string str_str_length("str-length"); + const sass::string str_insert("insert"); + const sass::string str_str_insert("str-insert"); + const sass::string str_index("index"); + const sass::string str_str_index("str-index"); + const sass::string str_slice("slice"); + const sass::string str_split("split"); + const sass::string str_str_slice("str-slice"); + const sass::string str_str_split("str-split"); + const sass::string str_unique_id("unique-id"); + + // For meta functions + const sass::string str_load_css("load-css"); + const sass::string str_feature_exists("feature-exists"); + const sass::string str_type_of("type-of"); + const sass::string str_inspect("inspect"); + const sass::string str_keywords("keywords"); + const sass::string str_if("if"); + const sass::string str_apply("apply"); + const sass::string str_calc_name("calc-name"); + const sass::string str_calc_args("calc-args"); + const sass::string str_get_mixin("get-mixin"); + const sass::string str_module_mixins("module-mixins"); + const sass::string str_accepts_content("accepts-content"); + const sass::string str_global_variable_exists("global-variable-exists"); + const sass::string str_variable_exists("variable-exists"); + const sass::string str_function_exists("function-exists"); + const sass::string str_mixin_exists("mixin-exists"); + const sass::string str_content_exists("content-exists"); + const sass::string str_module_variables("module-variables"); + const sass::string str_module_functions("module-functions"); + const sass::string str_get_function("get-function"); + const sass::string str_call("call"); + + // For color functions + const sass::string str_rgb("rgb"); + const sass::string str_rgba("rgba"); + const sass::string str_hsl("hsl"); + const sass::string str_hsla("hsla"); + const sass::string str_hwb("hwb"); + const sass::string str_hwba("hwba"); + const sass::string str_red("red"); + const sass::string str_green("green"); + const sass::string str_blue("blue"); + const sass::string str_hue("hue"); + const sass::string str_from("from"); + const sass::string str_lightness("lightness"); + const sass::string str_saturation("saturation"); + const sass::string str_blackness("blackness"); + const sass::string str_whiteness("whiteness"); + const sass::string str_invert("invert"); + const sass::string str_grayscale("grayscale"); + const sass::string str_complement("complement"); + const sass::string str_desaturate("desaturate"); + const sass::string str_saturate("saturate"); + const sass::string str_lighten("lighten"); + const sass::string str_darken("darken"); + const sass::string str_adjust_hue("adjust-hue"); + const sass::string str_adjust_color("adjust-color"); + const sass::string str_change_color("change-color"); + const sass::string str_scale_color("scale-color"); + const sass::string str_adjust("adjust"); + const sass::string str_change("change"); + const sass::string str_scale("scale"); + const sass::string str_mix("mix"); + const sass::string str_opacify("opacify"); + const sass::string str_fade_in("fade-in"); + const sass::string str_fade_out("fade-out"); + const sass::string str_transparentize("transparentize"); + const sass::string str_ie_hex_str("ie-hex-str"); + const sass::string str_alpha("alpha"); + const sass::string str_opacity("opacity"); + + // For math functions + const sass::string str_e("e"); + const sass::string str_pi("pi"); + const sass::string str_tau("tau"); + const sass::string str_epsilon("epsilon"); + const sass::string str_min_safe_integer("min-safe-integer"); + const sass::string str_max_safe_integer("max-safe-integer"); + const sass::string str_min_number("min-number"); + const sass::string str_max_number("max-number"); + + const sass::string str_infinity("infinity"); + const sass::string str_neg_infinity("-infinity"); + const sass::string str_nan("nan"); + + const sass::string str_ceil("ceil"); + const sass::string str_calc("calc"); + const sass::string str_clamp("clamp"); + const sass::string str_floor("floor"); + const sass::string str_max("max"); + const sass::string str_min("min"); + const sass::string str_round("round"); + const sass::string str_abs("abs"); + const sass::string str_exp("exp"); + const sass::string str_sign("sign"); + const sass::string str_hypot("hypot"); + const sass::string str_log("log"); + const sass::string str_div("div"); + const sass::string str_pow("pow"); + const sass::string str_sqrt("sqrt"); + const sass::string str_cos("cos"); + const sass::string str_sin("sin"); + const sass::string str_tan("tan"); + const sass::string str_acos("acos"); + const sass::string str_asin("asin"); + const sass::string str_atan("atan"); + const sass::string str_atan2("atan2"); + const sass::string str_mod("mod"); + const sass::string str_rem("rem"); + const sass::string str_random("random"); + const sass::string str_unit("unit"); + const sass::string str_percentage("percentage"); + const sass::string str_unitless("unitless"); + const sass::string str_is_unitless("is-unitless"); + const sass::string str_compatible("compatible"); + const sass::string str_comparable("comparable"); + const sass::string str_rad("rad"); + + // For selector functions + const sass::string str_nest("nest"); + const sass::string str_selector_nest("selector-nest"); + // const sass::string str_append("append"); + const sass::string str_selector_append("selector-append"); + const sass::string str_extend("extend"); + const sass::string str_selector_extend("selector-extend"); + const sass::string str_replace("replace"); + const sass::string str_selector_replace("selector-replace"); + const sass::string str_unify("unify"); + const sass::string str_selector_unify("selector-unify"); + const sass::string str_parse("parse"); + const sass::string str_selector_parse("selector-parse"); + const sass::string str_is_superselector("is-superselector"); + const sass::string str_simple_selectors("simple-selectors"); + + + + + const Units unit_rad(str_rad); + const Units unit_deg(str_deg); + const Units unit_percent("%"); + const Units unit_none; + + + + + + + + + + + // For list functions + const EnvKey key_length(str_length); + const EnvKey key_nth(str_nth); + const EnvKey key_set_nth(str_set_nth); + const EnvKey key_join(str_join); + const EnvKey key_append(str_append); + const EnvKey key_zip(str_zip); + const EnvKey key_slash(str_slash); + const EnvKey key_list_separator(str_list_separator); + const EnvKey key_is_bracketed(str_is_bracketed); + + // For map functions + const EnvKey key_set(str_set); + const EnvKey key_map_set(str_map_set); + const EnvKey key_get(str_get); + const EnvKey key_map_get(str_map_get); + const EnvKey key_merge(str_merge); + const EnvKey key_map_merge(str_map_merge); + const EnvKey key_remove(str_remove); + const EnvKey key_map_remove(str_map_remove); + const EnvKey key_keys(str_keys); + const EnvKey key_map_keys(str_map_keys); + const EnvKey key_values(str_values); + const EnvKey key_map_values(str_map_values); + const EnvKey key_has_key(str_has_key); + const EnvKey key_map_has_key(str_map_has_key); + const EnvKey key_deep_merge(str_deep_merge); + const EnvKey key_deep_remove(str_deep_remove); + + // For text functions + const EnvKey key_unquote(str_unquote); + const EnvKey key_quote(str_quote); + const EnvKey key_to_upper_case(str_to_upper_case); + const EnvKey key_to_lower_case(str_to_lower_case); + // const EnvKey key_length(str_length); + const EnvKey key_str_length(str_str_length); + const EnvKey key_insert(str_insert); + const EnvKey key_str_insert(str_str_insert); + const EnvKey key_index(str_index); + const EnvKey key_str_index(str_str_index); + const EnvKey key_slice(str_slice); + const EnvKey key_split(str_split); + const EnvKey key_str_slice(str_str_slice); + const EnvKey key_str_split(str_str_split); + const EnvKey key_unique_id(str_unique_id); + + // For meta functions + const EnvKey key_load_css(str_load_css); + const EnvKey key_feature_exists(str_feature_exists); + const EnvKey key_type_of(str_type_of); + const EnvKey key_inspect(str_inspect); + const EnvKey key_keywords(str_keywords); + const EnvKey key_if(str_if); + const EnvKey key_apply(str_apply); + const EnvKey key_calc_name(str_calc_name); + const EnvKey key_calc_args(str_calc_args); + const EnvKey key_get_mixin(str_get_mixin); + const EnvKey key_module_mixins(str_module_mixins); + const EnvKey key_accepts_content(str_accepts_content);; + const EnvKey key_global_variable_exists(str_global_variable_exists); + const EnvKey key_variable_exists(str_variable_exists); + const EnvKey key_function_exists(str_function_exists); + const EnvKey key_mixin_exists(str_mixin_exists); + const EnvKey key_content_exists(str_content_exists); + const EnvKey key_module_variables(str_module_variables); + const EnvKey key_module_functions(str_module_functions); + const EnvKey key_get_function(str_get_function); + const EnvKey key_call(str_call); + + // For color functions + const EnvKey key_rgb(str_rgb); + const EnvKey key_rgba(str_rgba); + const EnvKey key_hsl(str_hsl); + const EnvKey key_hsla(str_hsla); + const EnvKey key_hwb(str_hwb); + const EnvKey key_hwba(str_hwba); + const EnvKey key_red(str_red); + const EnvKey key_green(str_green); + const EnvKey key_blue(str_blue); + const EnvKey key_hue(str_hue); + const EnvKey key_lightness(str_lightness); + const EnvKey key_saturation(str_saturation); + const EnvKey key_blackness(str_blackness); + const EnvKey key_whiteness(str_whiteness); + const EnvKey key_invert(str_invert); + const EnvKey key_grayscale(str_grayscale); + const EnvKey key_complement(str_complement); + const EnvKey key_desaturate(str_desaturate); + const EnvKey key_saturate(str_saturate); + const EnvKey key_lighten(str_lighten); + const EnvKey key_darken(str_darken); + const EnvKey key_adjust_hue(str_adjust_hue); + const EnvKey key_adjust_color(str_adjust_color); + const EnvKey key_change_color(str_change_color); + const EnvKey key_scale_color(str_scale_color); + const EnvKey key_adjust(str_adjust); + const EnvKey key_change(str_change); + const EnvKey key_scale(str_scale); + const EnvKey key_mix(str_mix); + const EnvKey key_opacify(str_opacify); + const EnvKey key_fade_in(str_fade_in); + const EnvKey key_fade_out(str_fade_out); + const EnvKey key_transparentize(str_transparentize); + const EnvKey key_ie_hex_str(str_ie_hex_str); + const EnvKey key_alpha(str_alpha); + const EnvKey key_opacity(str_opacity); + + // For math functions + const EnvKey key_e(str_e); + const EnvKey key_pi(str_pi); + const EnvKey key_tau(str_tau); + const EnvKey key_epsilon(str_epsilon); + const EnvKey key_min_safe_integer(str_min_safe_integer); + const EnvKey key_max_safe_integer(str_max_safe_integer); + const EnvKey key_min_number(str_min_number); + const EnvKey key_max_number(str_max_number); + + + const EnvKey key_ceil(str_ceil); + const EnvKey key_clamp(str_clamp); + const EnvKey key_floor(str_floor); + const EnvKey key_max(str_max); + const EnvKey key_min(str_min); + const EnvKey key_round(str_round); + const EnvKey key_abs(str_abs); + const EnvKey key_hypot(str_hypot); + const EnvKey key_log(str_log); + const EnvKey key_div(str_div); + const EnvKey key_pow(str_pow); + const EnvKey key_sqrt(str_sqrt); + const EnvKey key_cos(str_cos); + const EnvKey key_sin(str_sin); + const EnvKey key_tan(str_tan); + const EnvKey key_acos(str_acos); + const EnvKey key_asin(str_asin); + const EnvKey key_atan(str_atan); + const EnvKey key_atan2(str_atan2); + const EnvKey key_random(str_random); + const EnvKey key_unit(str_unit); + const EnvKey key_percentage(str_percentage); + const EnvKey key_unitless(str_unitless); + const EnvKey key_is_unitless(str_is_unitless); + const EnvKey key_compatible(str_compatible); + const EnvKey key_comparable(str_comparable); + + // For selector functions + const EnvKey key_nest(str_nest); + const EnvKey key_selector_nest(str_selector_nest); + // const EnvKey key_append(str_append); + const EnvKey key_selector_append(str_selector_append); + const EnvKey key_extend(str_extend); + const EnvKey key_selector_extend(str_selector_extend); + const EnvKey key_replace(str_replace); + const EnvKey key_selector_replace(str_selector_replace); + const EnvKey key_unify(str_unify); + const EnvKey key_selector_unify(str_selector_unify); + const EnvKey key_parse(str_parse); + const EnvKey key_selector_parse(str_selector_parse); + const EnvKey key_is_superselector(str_is_superselector); + const EnvKey key_simple_selectors(str_simple_selectors); + + + namespace Strings + { + + const sass::string empty(""); + + // For list functions + const sass::string length("length"); + const sass::string nth("nth"); + const sass::string setNth("set-nth"); + const sass::string join("join"); + const sass::string append("append"); + const sass::string zip("zip"); + const sass::string listSeparator("list-separator"); + const sass::string isBracketed("is-bracketed"); + + + const sass::string plus("+"); + const sass::string minus("-"); + const sass::string percent("%"); + + const sass::string rgb("rgb"); + const sass::string hsl("hsl"); + const sass::string hwb("hwb"); + const sass::string rgba("rgba"); + const sass::string hsla("hsla"); + const sass::string hwba("hwba"); + + const sass::string deg("deg"); + const sass::string red("red"); + const sass::string hue("hue"); + const sass::string blue("blue"); + const sass::string green("green"); + const sass::string alpha("alpha"); + const sass::string color("color"); + const sass::string weight("weight"); + const sass::string number("number"); + const sass::string amount("amount"); + const sass::string invert("invert"); + const sass::string degrees("degrees"); + const sass::string saturate("saturate"); + const sass::string grayscale("grayscale"); + const sass::string whiteness("whiteness"); + const sass::string blackness("blackness"); + + const sass::string $whiteness("$whiteness"); + const sass::string $blackness("$blackness"); + + + + + const sass::string lightness("lightness"); + const sass::string saturation("saturation"); + + const sass::string key("key"); + const sass::string map("map"); + const sass::string map1("map1"); + const sass::string map2("map2"); + const sass::string args("args"); + const sass::string calc("calc"); + const sass::string null("null"); + const sass::string boolean("bool"); + const sass::string error("error"); + const sass::string warning("warning"); + const sass::string string("string"); + const sass::string function("function"); + const sass::string calculation("calculation"); + const sass::string calcoperation("calcoperation"); + const sass::string mixin("mixin"); + const sass::string arglist("arglist"); + + const sass::string url("url"); + const sass::string with("with"); + const sass::string list("list"); + const sass::string name("name"); + const sass::string media("media"); + const sass::string module("module"); + const sass::string supports("supports"); + const sass::string keyframes("keyframes"); + + + + const sass::string useRule("@use"); + const sass::string forRule("@for"); + const sass::string warnRule("@warn"); + const sass::string errorRule("@error"); + const sass::string debugRule("@debug"); + const sass::string extendRule("@extend"); + const sass::string importRule("@import"); + const sass::string contentRule("@content"); + const sass::string forwardRule("@forward"); + + const sass::string scaleColor("scale-color"); + const sass::string colorAdjust("color-adjust"); + const sass::string colorChange("color-change"); + + const sass::string condition("condition"); + const sass::string ifFalse("if-false"); + const sass::string ifTrue("if-true"); + + const sass::string $red("$red"); + const sass::string $green("$green"); + const sass::string $blue("$blue"); + + const sass::string $hue("$hue"); + const sass::string $saturation("$saturation"); + const sass::string $lightness("$lightness"); + + const sass::string utf8bom("\xEF\xBB\xBF"); + + const sass::string argument("argument"); + const sass::string _and_("and"); + const sass::string _or_("or"); + + } // namespace Strings + + namespace Keys { + + // For list functions + const EnvKey length(Strings::length); + const EnvKey nth(Strings::nth); + const EnvKey setNth(Strings::setNth); + const EnvKey join(Strings::join); + const EnvKey append(Strings::append); + const EnvKey zip(Strings::zip); + const EnvKey listSeparator(Strings::listSeparator); + const EnvKey isBracketed(Strings::isBracketed); + + const EnvKey red(Strings::red); + const EnvKey hue(Strings::hue); + const EnvKey blue(Strings::blue); + const EnvKey green(Strings::green); + const EnvKey alpha(Strings::alpha); + const EnvKey color(Strings::color); + const EnvKey lightness(Strings::lightness); + const EnvKey saturation(Strings::saturation); + + const EnvKey whiteness(Strings::whiteness); + const EnvKey blackness(Strings::blackness); + + + + + const EnvKey warnRule(Strings::warnRule); + const EnvKey errorRule(Strings::errorRule); + const EnvKey debugRule(Strings::debugRule); + const EnvKey contentRule(Strings::contentRule); + + const EnvKey condition(Strings::condition); + const EnvKey ifFalse(Strings::ifFalse); + const EnvKey ifTrue(Strings::ifTrue); + + } // namespace Keys + +} // namespace Sass diff --git a/src/strings.hpp b/src/strings.hpp new file mode 100644 index 0000000000..d60ff14d55 --- /dev/null +++ b/src/strings.hpp @@ -0,0 +1,525 @@ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#ifndef SASS_STRINGS_HPP +#define SASS_STRINGS_HPP + +// sass.hpp must go before all system headers +// to get the __EXTENSIONS__ fix on Solaris. +#include "capi_sass.hpp" + +// Include normalized keys +#include "environment_key.hpp" + +#include "units.hpp" + +namespace Sass { + + extern const sass::string str_empty; + + // For list functions + extern const sass::string str_length; + extern const sass::string str_nth; + extern const sass::string str_set_nth; + extern const sass::string str_join; + extern const sass::string str_append; + extern const sass::string str_zip; + extern const sass::string str_slash; + extern const sass::string str_list_separator; + extern const sass::string str_is_bracketed; + + // Rounding strategies + extern const sass::string str_up; + extern const sass::string str_down; + extern const sass::string str_nearest; + extern const sass::string str_to_zero; + + // For map functions + extern const sass::string str_set; + extern const sass::string str_map_set; + extern const sass::string str_get; + extern const sass::string str_map_get; + extern const sass::string str_merge; + extern const sass::string str_map_merge; + extern const sass::string str_remove; + extern const sass::string str_map_remove; + extern const sass::string str_keys; + extern const sass::string str_map_keys; + extern const sass::string str_base; + extern const sass::string str_deg; + extern const sass::string str_angle; + extern const sass::string str_number; + extern const sass::string str_value; + extern const sass::string str_values; + extern const sass::string str_map_values; + extern const sass::string str_has_key; + extern const sass::string str_map_has_key; + extern const sass::string str_deep_merge; + extern const sass::string str_deep_remove; + + // For text functions + extern const sass::string str_unquote; + extern const sass::string str_quote; + extern const sass::string str_to_upper_case; + extern const sass::string str_to_lower_case; + // extern const sass::string str_length; + extern const sass::string str_str_length; + extern const sass::string str_insert; + extern const sass::string str_str_insert; + extern const sass::string str_index; + extern const sass::string str_str_index; + extern const sass::string str_slice; + extern const sass::string str_split; + extern const sass::string str_str_slice; + extern const sass::string str_str_split; + extern const sass::string str_unique_id; + + // For meta functions + extern const sass::string str_load_css; + extern const sass::string str_feature_exists; + extern const sass::string str_type_of; + extern const sass::string str_inspect; + extern const sass::string str_keywords; + extern const sass::string str_if; + extern const sass::string str_apply; + extern const sass::string str_calc_name; + extern const sass::string str_calc_args; + extern const sass::string str_get_mixin; + extern const sass::string str_module_mixins; + extern const sass::string str_accepts_content; + extern const sass::string str_global_variable_exists; + extern const sass::string str_variable_exists; + extern const sass::string str_function_exists; + extern const sass::string str_mixin_exists; + extern const sass::string str_content_exists; + extern const sass::string str_module_variables; + extern const sass::string str_module_functions; + extern const sass::string str_get_function; + extern const sass::string str_call; + + // For color functions + extern const sass::string str_rgb; + extern const sass::string str_rgba; + extern const sass::string str_hsl; + extern const sass::string str_hsla; + extern const sass::string str_hwb; + extern const sass::string str_hwba; + extern const sass::string str_red; + extern const sass::string str_green; + extern const sass::string str_blue; + extern const sass::string str_hue; + extern const sass::string str_from; + extern const sass::string str_lightness; + extern const sass::string str_saturation; + extern const sass::string str_blackness; + extern const sass::string str_whiteness; + extern const sass::string str_invert; + extern const sass::string str_grayscale; + extern const sass::string str_complement; + extern const sass::string str_desaturate; + extern const sass::string str_saturate; + extern const sass::string str_lighten; + extern const sass::string str_darken; + extern const sass::string str_adjust_hue; + extern const sass::string str_adjust_color; + extern const sass::string str_change_color; + extern const sass::string str_scale_color; + extern const sass::string str_adjust; + extern const sass::string str_change; + extern const sass::string str_scale; + extern const sass::string str_mix; + extern const sass::string str_opacify; + extern const sass::string str_fade_in; + extern const sass::string str_fade_out; + extern const sass::string str_transparentize; + extern const sass::string str_ie_hex_str; + extern const sass::string str_alpha; + extern const sass::string str_opacity; + + // For math functions + extern const sass::string str_e; + extern const sass::string str_pi; + extern const sass::string str_tau; + extern const sass::string str_epsilon; + extern const sass::string str_min_safe_integer; + extern const sass::string str_max_safe_integer; + extern const sass::string str_min_number; + extern const sass::string str_max_number; + + extern const sass::string str_infinity; + extern const sass::string str_neg_infinity; + extern const sass::string str_nan; + + extern const sass::string str_ceil; + extern const sass::string str_calc; + extern const sass::string str_clamp; + extern const sass::string str_floor; + extern const sass::string str_max; + extern const sass::string str_min; + extern const sass::string str_round; + extern const sass::string str_abs; + extern const sass::string str_exp; + extern const sass::string str_sign; + extern const sass::string str_hypot; + extern const sass::string str_log; + extern const sass::string str_div; + extern const sass::string str_pow; + extern const sass::string str_sqrt; + extern const sass::string str_cos; + extern const sass::string str_sin; + extern const sass::string str_tan; + extern const sass::string str_acos; + extern const sass::string str_asin; + extern const sass::string str_atan; + extern const sass::string str_atan2; + extern const sass::string str_mod; + extern const sass::string str_rem; + extern const sass::string str_random; + extern const sass::string str_unit; + extern const sass::string str_percentage; + extern const sass::string str_unitless; + extern const sass::string str_is_unitless; + extern const sass::string str_compatible; + extern const sass::string str_comparable; + extern const sass::string str_rad; + + // For selector functions + extern const sass::string str_nest; + extern const sass::string str_selector_nest; + // extern const sass::string str_append; + extern const sass::string str_selector_append; + extern const sass::string str_extend; + extern const sass::string str_selector_extend; + extern const sass::string str_replace; + extern const sass::string str_selector_replace; + extern const sass::string str_unify; + extern const sass::string str_selector_unify; + extern const sass::string str_parse; + extern const sass::string str_selector_parse; + extern const sass::string str_is_superselector; + extern const sass::string str_simple_selectors; + + + + + + + + + + + + + + + + + + + + // For list functions + extern const EnvKey key_length; + extern const EnvKey key_nth; + extern const EnvKey key_set_nth; + extern const EnvKey key_join; + extern const EnvKey key_append; + extern const EnvKey key_zip; + extern const EnvKey key_slash; + extern const EnvKey key_list_separator; + extern const EnvKey key_is_bracketed; + + // For map functions + extern const EnvKey key_set; + extern const EnvKey key_map_set; + extern const EnvKey key_get; + extern const EnvKey key_map_get; + extern const EnvKey key_merge; + extern const EnvKey key_map_merge; + extern const EnvKey key_remove; + extern const EnvKey key_map_remove; + extern const EnvKey key_keys; + extern const EnvKey key_map_keys; + extern const EnvKey key_values; + extern const EnvKey key_map_values; + extern const EnvKey key_has_key; + extern const EnvKey key_map_has_key; + extern const EnvKey key_deep_merge; + extern const EnvKey key_deep_remove; + + // For text functions + extern const EnvKey key_unquote; + extern const EnvKey key_quote; + extern const EnvKey key_to_upper_case; + extern const EnvKey key_to_lower_case; + // extern const EnvKey key_length; + extern const EnvKey key_str_length; + extern const EnvKey key_insert; + extern const EnvKey key_str_insert; + extern const EnvKey key_index; + extern const EnvKey key_str_index; + extern const EnvKey key_slice; + extern const EnvKey key_split; + extern const EnvKey key_str_slice; + extern const EnvKey key_str_split; + extern const EnvKey key_unique_id; + + // For meta functions + extern const EnvKey key_load_css; + extern const EnvKey key_feature_exists; + extern const EnvKey key_type_of; + extern const EnvKey key_inspect; + extern const EnvKey key_keywords; + extern const EnvKey key_if; + extern const EnvKey key_apply; + extern const EnvKey key_calc_name; + extern const EnvKey key_calc_args; + extern const EnvKey key_get_mixin; + extern const EnvKey key_module_mixins; + extern const EnvKey key_accepts_content; + extern const EnvKey key_global_variable_exists; + extern const EnvKey key_variable_exists; + extern const EnvKey key_function_exists; + extern const EnvKey key_mixin_exists; + extern const EnvKey key_content_exists; + extern const EnvKey key_module_variables; + extern const EnvKey key_module_functions; + extern const EnvKey key_get_function; + extern const EnvKey key_call; + + // For color functions + extern const EnvKey key_rgb; + extern const EnvKey key_rgba; + extern const EnvKey key_hsl; + extern const EnvKey key_hsla; + extern const EnvKey key_hwb; + extern const EnvKey key_hwba; + extern const EnvKey key_red; + extern const EnvKey key_green; + extern const EnvKey key_blue; + extern const EnvKey key_hue; + extern const EnvKey key_lightness; + extern const EnvKey key_saturation; + extern const EnvKey key_blackness; + extern const EnvKey key_whiteness; + extern const EnvKey key_invert; + extern const EnvKey key_grayscale; + extern const EnvKey key_complement; + extern const EnvKey key_desaturate; + extern const EnvKey key_saturate; + extern const EnvKey key_lighten; + extern const EnvKey key_darken; + extern const EnvKey key_adjust_hue; + extern const EnvKey key_adjust_color; + extern const EnvKey key_change_color; + extern const EnvKey key_scale_color; + extern const EnvKey key_adjust; + extern const EnvKey key_change; + extern const EnvKey key_scale; + extern const EnvKey key_mix; + extern const EnvKey key_opacify; + extern const EnvKey key_fade_in; + extern const EnvKey key_fade_out; + extern const EnvKey key_transparentize; + extern const EnvKey key_ie_hex_str; + extern const EnvKey key_alpha; + extern const EnvKey key_opacity; + + // For math functions + extern const EnvKey key_e; + extern const EnvKey key_pi; + extern const EnvKey key_tau; + extern const EnvKey key_epsilon; + extern const EnvKey key_min_safe_integer; + extern const EnvKey key_max_safe_integer; + extern const EnvKey key_min_number; + extern const EnvKey key_max_number; + + extern const EnvKey key_ceil; + extern const EnvKey key_clamp; + extern const EnvKey key_floor; + extern const EnvKey key_max; + extern const EnvKey key_min; + extern const EnvKey key_round; + extern const EnvKey key_abs; + extern const EnvKey key_hypot; + extern const EnvKey key_log; + extern const EnvKey key_div; + extern const EnvKey key_pow; + extern const EnvKey key_sqrt; + extern const EnvKey key_cos; + extern const EnvKey key_sin; + extern const EnvKey key_tan; + extern const EnvKey key_acos; + extern const EnvKey key_asin; + extern const EnvKey key_atan; + extern const EnvKey key_atan2; + extern const EnvKey key_random; + extern const EnvKey key_unit; + extern const EnvKey key_percentage; + extern const EnvKey key_unitless; + extern const EnvKey key_is_unitless; + extern const EnvKey key_compatible; + extern const EnvKey key_comparable; + + // For selector functions + extern const EnvKey key_nest; + extern const EnvKey key_selector_nest; + // extern const EnvKey key_append; + extern const EnvKey key_selector_append; + extern const EnvKey key_extend; + extern const EnvKey key_selector_extend; + extern const EnvKey key_replace; + extern const EnvKey key_selector_replace; + extern const EnvKey key_unify; + extern const EnvKey key_selector_unify; + extern const EnvKey key_parse; + extern const EnvKey key_selector_parse; + extern const EnvKey key_is_superselector; + extern const EnvKey key_simple_selectors; + + + extern const Units unit_rad; + extern const Units unit_deg; + extern const Units unit_none; + extern const Units unit_percent; + + + + + + namespace Strings { + + extern const sass::string empty; + + // For list functions + extern const sass::string length; + extern const sass::string nth; + extern const sass::string setNth; + extern const sass::string join; + extern const sass::string append; + extern const sass::string zip; + extern const sass::string listSeparator; + extern const sass::string isBracketed; + + extern const sass::string plus; + extern const sass::string minus; + extern const sass::string percent; + + extern const sass::string rgb; + extern const sass::string hsl; + extern const sass::string hwb; + extern const sass::string rgba; + extern const sass::string hsla; + extern const sass::string hwba; + + extern const sass::string deg; + extern const sass::string red; + extern const sass::string hue; + extern const sass::string blue; + extern const sass::string green; + extern const sass::string alpha; + extern const sass::string color; + extern const sass::string weight; + extern const sass::string number; + extern const sass::string amount; + extern const sass::string invert; + extern const sass::string degrees; + extern const sass::string saturate; + extern const sass::string grayscale; + + extern const sass::string whiteness; + extern const sass::string blackness; + extern const sass::string $whiteness; + extern const sass::string $blackness; + + + + extern const sass::string lightness; + extern const sass::string saturation; + + + extern const sass::string key; + extern const sass::string map; + extern const sass::string map1; + extern const sass::string map2; + extern const sass::string args; + extern const sass::string calc; + extern const sass::string with; + extern const sass::string url; + extern const sass::string list; + extern const sass::string name; + extern const sass::string null; + extern const sass::string media; + extern const sass::string module; + extern const sass::string supports; + extern const sass::string keyframes; + + extern const sass::string boolean; + extern const sass::string string; + extern const sass::string arglist; + extern const sass::string function; + extern const sass::string calculation; + extern const sass::string calcoperation; + extern const sass::string mixin; + extern const sass::string error; + extern const sass::string warning; + + extern const sass::string useRule; + extern const sass::string forRule; + extern const sass::string warnRule; + extern const sass::string errorRule; + extern const sass::string debugRule; + extern const sass::string extendRule; + extern const sass::string importRule; + extern const sass::string contentRule; + extern const sass::string forwardRule; + + extern const sass::string scaleColor; + extern const sass::string colorAdjust; + extern const sass::string colorChange; + + extern const sass::string condition; + extern const sass::string ifFalse; + extern const sass::string ifTrue; + + extern const sass::string $red; + extern const sass::string $green; + extern const sass::string $blue; + + extern const sass::string $hue; + extern const sass::string $saturation; + extern const sass::string $lightness; + + extern const sass::string utf8bom; + + extern const sass::string argument; + extern const sass::string _and_; + extern const sass::string _or_; + + } + + + namespace Keys { + + + + + extern const EnvKey alpha; + extern const EnvKey color; + + + extern const EnvKey warnRule; + extern const EnvKey errorRule; + extern const EnvKey debugRule; + extern const EnvKey contentRule; + + extern const EnvKey condition; + extern const EnvKey ifFalse; + extern const EnvKey ifTrue; + + } + +} + + +#endif diff --git a/src/stylesheet.cpp b/src/stylesheet.cpp index e0e4345c29..84d9fd2000 100644 --- a/src/stylesheet.cpp +++ b/src/stylesheet.cpp @@ -1,22 +1,36 @@ -// sass.hpp must go before all system headers to get the -// __EXTENSIONS__ fix on Solaris. -#include "sass.hpp" - +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ #include "stylesheet.hpp" namespace Sass { - // Constructor - Sass::StyleSheet::StyleSheet(const Resource& res, Block_Obj root) : - Resource(res), - root(root) - { - } + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + Root::Root(const SourceSpan& pstate, size_t reserve) + : AstNode(pstate), Vectorized(reserve), Module(nullptr) + {} - StyleSheet::StyleSheet(const StyleSheet& sheet) : - Resource(sheet), - root(sheet.root) + Root::Root(const SourceSpan& pstate, StatementVector&& vec) + : AstNode(pstate), Vectorized(std::move(vec)), Module(nullptr) + {} + + void Root::addExtension( + const SelectorListObj& extender3, + const SimpleSelectorObj& target, + const CssMediaRuleObj& mediaQueryContext, + const ExtendRuleObj& extend, + bool is_optional) { +//UUU for (Root* mod : upstream) { +//UUU mod->addExtension(extend, target, mediaQueryContext, is_optional); +//UUU } + if (extender) extender->addExtension(extender3, target, mediaQueryContext, extend, is_optional); } + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + } diff --git a/src/stylesheet.hpp b/src/stylesheet.hpp index 37e85772a8..3a66bb687a 100644 --- a/src/stylesheet.hpp +++ b/src/stylesheet.hpp @@ -1,57 +1,47 @@ -#ifndef SASS_STYLESHEET_H -#define SASS_STYLESHEET_H +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#ifndef SASS_STYLESHEET_HPP +#define SASS_STYLESHEET_HPP -// sass.hpp must go before all system headers to get the -// __EXTENSIONS__ fix on Solaris. -#include "sass.hpp" +// sass.hpp must go before all system headers +// to get the __EXTENSIONS__ fix on Solaris. +#include "capi_sass.hpp" -#include "ast_fwd_decl.hpp" -#include "extender.hpp" -#include "file.hpp" +#include "ast_css.hpp" +#include "modules.hpp" +#include "import.hpp" namespace Sass { // parsed stylesheet from loaded resource // this should be a `Module` for sass 4.0 - class StyleSheet : public Resource { - public: + class Root final : public AstNode, + public Vectorized, + public Module + { + 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; + // sass::vector upstreams; - // Modules that this module uses. - // List get upstream; + // Import object through which this module was loaded. + // It also has the input type (css vs sass) attached + ImportObj import; - // The module's variables. - // Map get variables; + Root(const SourceSpan& pstate, size_t reserve = 0); - // The module's functions. Implementations must ensure - // that each [Callable] is stored under its own name. - // Map get functions; + Root(const SourceSpan& pstate, StatementVector&& vec); - // The module's mixins. Implementations must ensure that - // each [Callable] is stored under its own name. - // Map get mixins; + void addExtension( + const SelectorListObj& extender, + const SimpleSelectorObj& target, + const CssMediaRuleObj& mediaQueryContext, + const ExtendRuleObj& extend, + bool is_optional); - // 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/terminal.cpp b/src/terminal.cpp new file mode 100644 index 0000000000..c7505cf6fb --- /dev/null +++ b/src/terminal.cpp @@ -0,0 +1,271 @@ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#include "terminal.hpp" + +#ifndef _WIN32 +#include +#include +#include +#include +#include +#include +#endif + +// Minimal terminal abstraction for cross compatibility. +// Its main purpose is to let us print stuff with colors. +namespace Terminal { + + // Query number of available console columns + // Useful to shorten our output to fit nicely + short getColumns(bool error) + { + // First check if console is attached + if (!isConsoleAttached(error)) { + return SassDefaultColumns; + } + #ifdef _WIN32 + DWORD fd = error + ? STD_ERROR_HANDLE + : STD_OUTPUT_HANDLE; + // Get information of the screen buffer + CONSOLE_SCREEN_BUFFER_INFO csbi; + HANDLE handle = GetStdHandle(fd); + GetConsoleScreenBufferInfo(handle, &csbi); + // csbi.srWindow.Right - csbi.srWindow.Left + return csbi.dwMaximumWindowSize.X; + #else + int fd = open("/dev/tty", O_RDWR); + struct winsize ws; + if (fd < 0 || ioctl(fd, TIOCGWINSZ, &ws) < 0) { + return SassDefaultColumns; + } + close(fd); + return ws.ws_col; + #endif + } + // EO getColumns + + // Check if we are actually printing to the console + // In all other cases we want monochrome ASCII output + bool isConsoleAttached(bool error) + { + #ifdef _WIN32 + DWORD fd = error + ? STD_ERROR_HANDLE + : STD_OUTPUT_HANDLE; + if (GetConsoleCP() == 0) return false; + HANDLE handle = GetStdHandle(fd); + if (handle == INVALID_HANDLE_VALUE) return false; + DWORD filetype = GetFileType(handle); + return filetype == FILE_TYPE_CHAR; + #else + return isatty(fileno(error ? stderr : stdout)); + #endif + } + // EO isConsoleAttached + + // Check that we print to a terminal with unicode support + bool hasUnicodeSupport(bool error) + { + #ifdef _WIN32 + UINT cp = GetConsoleOutputCP(); + if (cp == 0) return false; + return cp == 65001; + #else + return false; + #endif + } + // EO hasUnicodeSupport + + // Check that we print to a terminal with color support + bool hasColorSupport(bool error) + { + #ifdef _WIN32 + return isConsoleAttached(error); + #else + return isConsoleAttached(error); + #endif + } + // EO hasColorSupport + + // This function is able to print a line with colors + // It translates the ANSI terminal codes to windows + void print(const char* output, bool error) + { + + if (output == 0) return; + + #ifdef _WIN32 + + DWORD fd = error + ? STD_ERROR_HANDLE + : STD_OUTPUT_HANDLE; + + HANDLE handle = GetStdHandle(fd); + CONSOLE_SCREEN_BUFFER_INFO info = {}; + GetConsoleScreenBufferInfo(handle, &info); + WORD attribute = info.wAttributes; + + while (output[0] != '\0') { + + if (output[0] == '\x1B' && output[1] == '[') { + + output += 2; + int one = 0; + int two = 0; + + attribute &= ~(FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_INTENSITY); + attribute &= ~(BACKGROUND_BLUE | BACKGROUND_GREEN | BACKGROUND_RED | BACKGROUND_INTENSITY); + + while (output[0] != '\0' && output[0] != ';' && output[0] != 'm') { + one *= 10; + switch (*output) { + case '0': one += 0; break; + case '1': one += 1; break; + case '2': one += 2; break; + case '3': one += 3; break; + case '4': one += 4; break; + case '5': one += 5; break; + case '6': one += 6; break; + case '7': one += 7; break; + case '8': one += 8; break; + case '9': one += 9; break; + default: break; + } + output += 1; + } + + if (one == 31 || one == 33 || one == 35 || one == 37 || one == 0) attribute |= FOREGROUND_RED; + if (one == 32 || one == 33 || one == 36 || one == 37 || one == 0) attribute |= FOREGROUND_GREEN; + if (one == 34 || one == 35 || one == 36 || one == 37 || one == 0) attribute |= FOREGROUND_BLUE; + if (one == 41 || one == 43 || one == 45 || one == 47) attribute |= BACKGROUND_RED; + if (one == 42 || one == 43 || one == 46 || one == 47) attribute |= BACKGROUND_GREEN; + if (one == 44 || one == 45 || one == 46 || one == 47) attribute |= BACKGROUND_BLUE; + + if (output[0] == ';') { + + output += 1; + + while (output[0] != '\0' && output[0] != ';' && output[0] != 'm') { + two *= 10; + switch (*output) { + case '0': two += 0; break; + case '1': two += 1; break; + case '2': two += 2; break; + case '3': two += 3; break; + case '4': two += 4; break; + case '5': two += 5; break; + case '6': two += 6; break; + case '7': two += 7; break; + case '8': two += 8; break; + case '9': two += 9; break; + default: break; + } + output += 1; + } + + if (two == 31 || two == 33 || two == 35 || two == 37 || two == 0) attribute |= FOREGROUND_RED; + if (two == 32 || two == 33 || two == 36 || two == 37 || two == 0) attribute |= FOREGROUND_GREEN; + if (two == 34 || two == 35 || two == 36 || two == 37 || two == 0) attribute |= FOREGROUND_BLUE; + if (two == 41 || two == 43 || two == 45 || two == 47) attribute |= BACKGROUND_RED; + if (two == 42 || two == 43 || two == 46 || two == 47) attribute |= BACKGROUND_GREEN; + if (two == 44 || two == 45 || two == 46 || two == 47) attribute |= BACKGROUND_BLUE; + + } + + if (one == 1 && two > 30 && two < 50) attribute |= FOREGROUND_INTENSITY; + if (one == 1 && two > 40 && two < 50) attribute |= BACKGROUND_INTENSITY; + + // attribute = BACKGROUND_GREEN | BACKGROUND_INTENSITY; + + if (output[0] == 'm') output += 1; + // Flush before setting new color + // ToDo: there might be a better way + fflush(error ? stderr : stdout); + SetConsoleTextAttribute(handle, attribute); + + } + // else if (output[0] == '\r' && output[1] == '\n') { + // output += 1; + // } + else { + + if (error) { + std::cerr << output[0]; + if (output[0] == '\n') fflush(stderr); + } + else { + std::cout << output[0]; + if (output[0] == '\n') fflush(stdout); + } + output += 1; + } + + } + + fflush(error ? stderr : stdout); + + #else + + if (error) { + std::cerr << output; + } + else { + std::cout << output; + } + + #endif + + } + // EO print + + // Count number of printable bytes/characters + size_t count_printable(const char* string) + { + size_t count = 0; + while (string && *string) { + if (string[0] == '\x1b' && string[1] == '[') { + while (*string != 0 && *string != 'm') { + string++; + } + string++; + } + else { + string += 1; + count += 1; + } + } + return count; + } + // EO count_printable + + // Code for color testing rainbow for debugging + // + // stream << getopt->compiler.getTerm(Terminal::red) << "red" << getopt->compiler.getTerm(Terminal::reset) << "\n"; + // stream << getopt->compiler.getTerm(Terminal::green) << "green" << getopt->compiler.getTerm(Terminal::reset) << "\n"; + // stream << getopt->compiler.getTerm(Terminal::yellow) << "yellow" << getopt->compiler.getTerm(Terminal::reset) << "\n"; + // stream << getopt->compiler.getTerm(Terminal::blue) << "blue" << getopt->compiler.getTerm(Terminal::reset) << "\n"; + // stream << getopt->compiler.getTerm(Terminal::magenta) << "magenta" << getopt->compiler.getTerm(Terminal::reset) << "\n"; + // stream << getopt->compiler.getTerm(Terminal::cyan) << "cyan" << getopt->compiler.getTerm(Terminal::reset) << "\n"; + // stream << getopt->compiler.getTerm(Terminal::bold_red) << "bold_red" << getopt->compiler.getTerm(Terminal::reset) << "\n"; + // stream << getopt->compiler.getTerm(Terminal::bold_green) << "bold_green" << getopt->compiler.getTerm(Terminal::reset) << "\n"; + // stream << getopt->compiler.getTerm(Terminal::bold_yellow) << "bold_yellow" << getopt->compiler.getTerm(Terminal::reset) << "\n"; + // stream << getopt->compiler.getTerm(Terminal::bold_blue) << "bold_blue" << getopt->compiler.getTerm(Terminal::reset) << "\n"; + // stream << getopt->compiler.getTerm(Terminal::bold_magenta) << "bold_magenta" << getopt->compiler.getTerm(Terminal::reset) << "\n"; + // stream << getopt->compiler.getTerm(Terminal::bold_cyan) << "bold_cyan" << getopt->compiler.getTerm(Terminal::reset) << "\n"; + // stream << getopt->compiler.getTerm(Terminal::bg_red) << "bg_red" << getopt->compiler.getTerm(Terminal::reset) << "\n"; + // stream << getopt->compiler.getTerm(Terminal::bg_green) << "bg_green" << getopt->compiler.getTerm(Terminal::reset) << "\n"; + // stream << getopt->compiler.getTerm(Terminal::bg_yellow) << "bg_yellow" << getopt->compiler.getTerm(Terminal::reset) << "\n"; + // stream << getopt->compiler.getTerm(Terminal::bg_blue) << "bg_blue" << getopt->compiler.getTerm(Terminal::reset) << "\n"; + // stream << getopt->compiler.getTerm(Terminal::bg_magenta) << "bg_magenta" << getopt->compiler.getTerm(Terminal::reset) << "\n"; + // stream << getopt->compiler.getTerm(Terminal::bg_cyan) << "bg_cyan" << getopt->compiler.getTerm(Terminal::reset) << "\n"; + // stream << getopt->compiler.getTerm(Terminal::bg_bold_red) << "bg_bold_red" << getopt->compiler.getTerm(Terminal::reset) << "\n"; + // stream << getopt->compiler.getTerm(Terminal::bg_bold_green) << "bg_bold_green" << getopt->compiler.getTerm(Terminal::reset) << "\n"; + // stream << getopt->compiler.getTerm(Terminal::bg_bold_yellow) << "bg_bold_yellow" << getopt->compiler.getTerm(Terminal::reset) << "\n"; + // stream << getopt->compiler.getTerm(Terminal::bg_bold_blue) << "bg_bold_blue" << getopt->compiler.getTerm(Terminal::reset) << "\n"; + // stream << getopt->compiler.getTerm(Terminal::bg_bold_magenta) << "bg_bold_magenta" << getopt->compiler.getTerm(Terminal::reset) << "\n"; + // stream << getopt->compiler.getTerm(Terminal::bg_bold_cyan) << "bg_bold_cyan" << getopt->compiler.getTerm(Terminal::reset) << "\n"; + + +} diff --git a/src/terminal.hpp b/src/terminal.hpp new file mode 100644 index 0000000000..9117910702 --- /dev/null +++ b/src/terminal.hpp @@ -0,0 +1,51 @@ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#ifndef SASS_TERMINAL_HPP +#define SASS_TERMINAL_HPP + +// sass.hpp must go before all system headers +// to get the __EXTENSIONS__ fix on Solaris. +#include "capi_sass.hpp" + +#ifdef _WIN32 +#include +#include +#include +#include +#include +#endif + +#include "constants.hpp" + +// Minimal terminal abstraction for cross compatibility. +// Its main purpose is to let us print stuff with colors. +namespace Terminal { + + // Import constant terminal color definition + using namespace Sass::Constants::Terminal; + + // Query number of available console columns + // Useful to shorten our output to fit nicely + short getColumns(bool error = false); + + // Check if we are actually printing to the console + // In all other cases we want monochrome ASCII output + bool isConsoleAttached(bool error = false); + + // Check that we print to a terminal with unicode support + bool hasUnicodeSupport(bool error = false); + + // Check that we print to a terminal with color support + bool hasColorSupport(bool error = false); + + // This function is able to print a line with colors + // It translates the ANSI terminal codes to windows + void print(const char* output, bool error = false); + + // Count number of printable bytes/characters + size_t count_printable(const char* string); + +} + +#endif diff --git a/src/terminal/windows.hpp b/src/terminal/windows.hpp new file mode 100644 index 0000000000..e4afffa808 --- /dev/null +++ b/src/terminal/windows.hpp @@ -0,0 +1,15 @@ +#include +#include + +#define LOG_COLOR_WHITE 7 +#define COLOR_GREEN 10 +#define COLOR_YELLOW 14 +#define COLOR_MAGENTA 13 + +hConsole = GetStdHandle(STD_OUTPUT_HANDLE); + +namespace Sass { + + + +} diff --git a/src/tessil/bhopscotch_map.h b/src/tessil/bhopscotch_map.h new file mode 100644 index 0000000000..582fefd81d --- /dev/null +++ b/src/tessil/bhopscotch_map.h @@ -0,0 +1,706 @@ +/** + * MIT License + * + * Copyright (c) 2017 Tessil + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef TSL_BHOPSCOTCH_MAP_H +#define TSL_BHOPSCOTCH_MAP_H + + +#include +#include +#include +#include +#include +#include +#include +#include +#include "hopscotch_hash.h" + + +namespace tsl { + + +/** + * Similar to tsl::hopscotch_map but instead of using a list for overflowing elements it uses + * a binary search tree. It thus needs an additional template parameter Compare. Compare should + * be arithmetically coherent with KeyEqual. + * + * The binary search tree allows the map to have a worst-case scenario of O(log n) for search + * and delete, even if the hash function maps all the elements to the same bucket. + * For insert, the amortized worst case is O(log n), but the worst case is O(n) in case of rehash. + * + * This makes the map resistant to DoS attacks (but doesn't preclude you to have a good hash function, + * as an element in the bucket array is faster to retrieve than in the tree). + * + * @copydoc hopscotch_map + */ +template, + class KeyEqual = std::equal_to, + class Compare = std::less, + class Allocator = std::allocator>, + unsigned int NeighborhoodSize = 62, + bool StoreHash = false, + class GrowthPolicy = tsl::hh::power_of_two_growth_policy<2>> +class bhopscotch_map { +private: + template + using has_is_transparent = tsl::detail_hopscotch_hash::has_is_transparent; + + class KeySelect { + public: + using key_type = Key; + + const key_type& operator()(const std::pair& key_value) const { + return key_value.first; + } + + const key_type& operator()(std::pair& key_value) { + return key_value.first; + } + }; + + class ValueSelect { + public: + using value_type = T; + + const value_type& operator()(const std::pair& key_value) const { + return key_value.second; + } + + value_type& operator()(std::pair& key_value) { + return key_value.second; + } + }; + + + // TODO Not optimal as we have to use std::pair as ValueType which forbid + // us to move the key in the bucket array, we have to use copy. Optimize. + using overflow_container_type = std::map; + using ht = detail_hopscotch_hash::hopscotch_hash, KeySelect, ValueSelect, + Hash, KeyEqual, + Allocator, NeighborhoodSize, + StoreHash, GrowthPolicy, + overflow_container_type>; + +public: + using key_type = typename ht::key_type; + using mapped_type = T; + using value_type = typename ht::value_type; + using size_type = typename ht::size_type; + using difference_type = typename ht::difference_type; + using hasher = typename ht::hasher; + using key_equal = typename ht::key_equal; + using key_compare = Compare; + using allocator_type = typename ht::allocator_type; + using reference = typename ht::reference; + using const_reference = typename ht::const_reference; + using pointer = typename ht::pointer; + using const_pointer = typename ht::const_pointer; + using iterator = typename ht::iterator; + using const_iterator = typename ht::const_iterator; + + + /* + * Constructors + */ + bhopscotch_map() : bhopscotch_map(ht::DEFAULT_INIT_BUCKETS_SIZE) { + } + + explicit bhopscotch_map(size_type bucket_count, + const Hash& hash = Hash(), + const KeyEqual& equal = KeyEqual(), + const Allocator& alloc = Allocator(), + const Compare& comp = Compare()) : + m_ht(bucket_count, hash, equal, alloc, ht::DEFAULT_MAX_LOAD_FACTOR, comp) + { + } + + bhopscotch_map(size_type bucket_count, + const Allocator& alloc) : bhopscotch_map(bucket_count, Hash(), KeyEqual(), alloc) + { + } + + bhopscotch_map(size_type bucket_count, + const Hash& hash, + const Allocator& alloc) : bhopscotch_map(bucket_count, hash, KeyEqual(), alloc) + { + } + + explicit bhopscotch_map(const Allocator& alloc) : bhopscotch_map(ht::DEFAULT_INIT_BUCKETS_SIZE, alloc) { + } + + template + bhopscotch_map(InputIt first, InputIt last, + size_type bucket_count = ht::DEFAULT_INIT_BUCKETS_SIZE, + const Hash& hash = Hash(), + const KeyEqual& equal = KeyEqual(), + const Allocator& alloc = Allocator()) : bhopscotch_map(bucket_count, hash, equal, alloc) + { + insert(first, last); + } + + template + bhopscotch_map(InputIt first, InputIt last, + size_type bucket_count, + const Allocator& alloc) : bhopscotch_map(first, last, bucket_count, Hash(), KeyEqual(), alloc) + { + } + + template + bhopscotch_map(InputIt first, InputIt last, + size_type bucket_count, + const Hash& hash, + const Allocator& alloc) : bhopscotch_map(first, last, bucket_count, hash, KeyEqual(), alloc) + { + } + + bhopscotch_map(std::initializer_list init, + size_type bucket_count = ht::DEFAULT_INIT_BUCKETS_SIZE, + const Hash& hash = Hash(), + const KeyEqual& equal = KeyEqual(), + const Allocator& alloc = Allocator()) : + bhopscotch_map(init.begin(), init.end(), bucket_count, hash, equal, alloc) + { + } + + bhopscotch_map(std::initializer_list init, + size_type bucket_count, + const Allocator& alloc) : + bhopscotch_map(init.begin(), init.end(), bucket_count, Hash(), KeyEqual(), alloc) + { + } + + bhopscotch_map(std::initializer_list init, + size_type bucket_count, + const Hash& hash, + const Allocator& alloc) : + bhopscotch_map(init.begin(), init.end(), bucket_count, hash, KeyEqual(), alloc) + { + } + + + bhopscotch_map& operator=(std::initializer_list ilist) { + m_ht.clear(); + + m_ht.reserve(ilist.size()); + m_ht.insert(ilist.begin(), ilist.end()); + + return *this; + } + + allocator_type get_allocator() const { return m_ht.get_allocator(); } + + + /* + * Iterators + */ + iterator begin() noexcept { return m_ht.begin(); } + const_iterator begin() const noexcept { return m_ht.begin(); } + const_iterator cbegin() const noexcept { return m_ht.cbegin(); } + + iterator end() noexcept { return m_ht.end(); } + const_iterator end() const noexcept { return m_ht.end(); } + const_iterator cend() const noexcept { return m_ht.cend(); } + + + /* + * Capacity + */ + bool empty() const noexcept { return m_ht.empty(); } + size_type size() const noexcept { return m_ht.size(); } + size_type max_size() const noexcept { return m_ht.max_size(); } + + /* + * Modifiers + */ + void clear() noexcept { m_ht.clear(); } + + + + + std::pair insert(const value_type& value) { + return m_ht.insert(value); + } + + template::value>::type* = nullptr> + std::pair insert(P&& value) { + return m_ht.insert(std::forward

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

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

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

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

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

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

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

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

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

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

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

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

(value)); + } + + iterator insert(const_iterator hint, value_type&& value) { + return m_ht.insert_hint(hint, std::move(value)); + } + + + template + void insert(InputIt first, InputIt last) { m_ht.insert(first, last); } + void insert(std::initializer_list ilist) { m_ht.insert(ilist.begin(), ilist.end()); } + + + + + template + std::pair insert_or_assign(const key_type& k, M&& obj) { + return m_ht.insert_or_assign(k, std::forward(obj)); + } + + template + std::pair insert_or_assign(key_type&& k, M&& obj) { + return m_ht.insert_or_assign(std::move(k), std::forward(obj)); + } + + + template + iterator insert_or_assign(const_iterator hint, const key_type& k, M&& obj) { + return m_ht.insert_or_assign(hint, k, std::forward(obj)); + } + + template + iterator insert_or_assign(const_iterator hint, key_type&& k, M&& obj) { + return m_ht.insert_or_assign(hint, std::move(k), std::forward(obj)); + } + + /** + * Due to the way elements are stored, emplace will need to move or copy the key-value once. + * The method is equivalent to insert(value_type(std::forward(args)...)); + * + * Mainly here for compatibility with the std::unordered_map interface. + */ + template + std::pair emplace(Args&&... args) { return m_ht.emplace(std::forward(args)...); } + + /** + * Due to the way elements are stored, emplace_hint will need to move or copy the key-value once. + * The method is equivalent to insert(hint, value_type(std::forward(args)...)); + * + * Mainly here for compatibility with the std::unordered_map interface. + */ + template + iterator emplace_hint(const_iterator hint, Args&&... args) { + return m_ht.emplace_hint(hint, std::forward(args)...); + } + + + + + template + std::pair try_emplace(const key_type& k, Args&&... args) { + return m_ht.try_emplace(k, std::forward(args)...); + } + + template + std::pair try_emplace(key_type&& k, Args&&... args) { + return m_ht.try_emplace(std::move(k), std::forward(args)...); + } + + template + iterator try_emplace(const_iterator hint, const key_type& k, Args&&... args) { + return m_ht.try_emplace_hint(hint, k, std::forward(args)...); + } + + template + iterator try_emplace(const_iterator hint, key_type&& k, Args&&... args) { + return m_ht.try_emplace_hint(hint, std::move(k), std::forward(args)...); + } + + + + + /** + * When erasing an element, the insert order will be preserved and no holes will be present in the container + * returned by 'values_container()'. + * + * The method is in O(n), if the order is not important 'unordered_erase(...)' method is faster with an O(1) + * average complexity. + */ + iterator erase(iterator pos) { return m_ht.erase(pos); } + + /** + * @copydoc erase(iterator pos) + */ + iterator erase(const_iterator pos) { return m_ht.erase(pos); } + + /** + * @copydoc erase(iterator pos) + */ + iterator erase(const_iterator first, const_iterator last) { return m_ht.erase(first, last); } + + /** + * @copydoc erase(iterator pos) + */ + size_type erase(const key_type& key) { return m_ht.erase(key); } + + /** + * @copydoc erase(iterator pos) + * + * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same + * as hash_function()(key). Usefull to speed-up the lookup to the value if you already have the hash. + */ + size_type erase(const key_type& key, std::size_t precalculated_hash) { + return m_ht.erase(key, precalculated_hash); + } + + /** + * @copydoc erase(iterator pos) + * + * This overload only participates in the overload resolution if the typedef KeyEqual::is_transparent exists. + * If so, K must be hashable and comparable to Key. + */ + template::value>::type* = nullptr> + size_type erase(const K& key) { return m_ht.erase(key); } + + /** + * @copydoc erase(const key_type& key, std::size_t precalculated_hash) + * + * This overload only participates in the overload resolution if the typedef KeyEqual::is_transparent exists. + * If so, K must be hashable and comparable to Key. + */ + template::value>::type* = nullptr> + size_type erase(const K& key, std::size_t precalculated_hash) { + return m_ht.erase(key, precalculated_hash); + } + + + + void swap(ordered_map& other) { other.m_ht.swap(m_ht); } + + /* + * Lookup + */ + T& at(const Key& key) { return m_ht.at(key); } + + /** + * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same + * as hash_function()(key). Usefull to speed-up the lookup if you already have the hash. + */ + T& at(const Key& key, std::size_t precalculated_hash) { return m_ht.at(key, precalculated_hash); } + + + const T& at(const Key& key) const { return m_ht.at(key); } + + /** + * @copydoc at(const Key& key, std::size_t precalculated_hash) + */ + const T& at(const Key& key, std::size_t precalculated_hash) const { return m_ht.at(key, precalculated_hash); } + + + /** + * This overload only participates in the overload resolution if the typedef KeyEqual::is_transparent exists. + * If so, K must be hashable and comparable to Key. + */ + template::value>::type* = nullptr> + T& at(const K& key) { return m_ht.at(key); } + + /** + * @copydoc at(const K& key) + * + * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same + * as hash_function()(key). Usefull to speed-up the lookup if you already have the hash. + */ + template::value>::type* = nullptr> + T& at(const K& key, std::size_t precalculated_hash) { return m_ht.at(key, precalculated_hash); } + + /** + * @copydoc at(const K& key) + */ + template::value>::type* = nullptr> + const T& at(const K& key) const { return m_ht.at(key); } + + /** + * @copydoc at(const K& key, std::size_t precalculated_hash) + */ + template::value>::type* = nullptr> + const T& at(const K& key, std::size_t precalculated_hash) const { return m_ht.at(key, precalculated_hash); } + + + + T& operator[](const Key& key) { return m_ht[key]; } + T& operator[](Key&& key) { return m_ht[std::move(key)]; } + + + + size_type count(const Key& key) const { return m_ht.count(key); } + + /** + * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same + * as hash_function()(key). Usefull to speed-up the lookup if you already have the hash. + */ + size_type count(const Key& key, std::size_t precalculated_hash) const { + return m_ht.count(key, precalculated_hash); + } + + /** + * This overload only participates in the overload resolution if the typedef KeyEqual::is_transparent exists. + * If so, K must be hashable and comparable to Key. + */ + template::value>::type* = nullptr> + size_type count(const K& key) const { return m_ht.count(key); } + + /** + * @copydoc count(const K& key) const + * + * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same + * as hash_function()(key). Usefull to speed-up the lookup if you already have the hash. + */ + template::value>::type* = nullptr> + size_type count(const K& key, std::size_t precalculated_hash) const { + return m_ht.count(key, precalculated_hash); + } + + + + iterator find(const Key& key) { return m_ht.find(key); } + + /** + * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same + * as hash_function()(key). Usefull to speed-up the lookup if you already have the hash. + */ + iterator find(const Key& key, std::size_t precalculated_hash) { return m_ht.find(key, precalculated_hash); } + + const_iterator find(const Key& key) const { return m_ht.find(key); } + + /** + * @copydoc find(const Key& key, std::size_t precalculated_hash) + */ + const_iterator find(const Key& key, std::size_t precalculated_hash) const { + return m_ht.find(key, precalculated_hash); + } + + /** + * This overload only participates in the overload resolution if the typedef KeyEqual::is_transparent exists. + * If so, K must be hashable and comparable to Key. + */ + template::value>::type* = nullptr> + iterator find(const K& key) { return m_ht.find(key); } + + /** + * @copydoc find(const K& key) + * + * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same + * as hash_function()(key). Usefull to speed-up the lookup if you already have the hash. + */ + template::value>::type* = nullptr> + iterator find(const K& key, std::size_t precalculated_hash) { return m_ht.find(key, precalculated_hash); } + + /** + * @copydoc find(const K& key) + */ + template::value>::type* = nullptr> + const_iterator find(const K& key) const { return m_ht.find(key); } + + /** + * @copydoc find(const K& key) + * + * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same + * as hash_function()(key). Usefull to speed-up the lookup if you already have the hash. + */ + template::value>::type* = nullptr> + const_iterator find(const K& key, std::size_t precalculated_hash) const { + return m_ht.find(key, precalculated_hash); + } + + + + std::pair equal_range(const Key& key) { return m_ht.equal_range(key); } + + /** + * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same + * as hash_function()(key). Usefull to speed-up the lookup if you already have the hash. + */ + std::pair equal_range(const Key& key, std::size_t precalculated_hash) { + return m_ht.equal_range(key, precalculated_hash); + } + + std::pair equal_range(const Key& key) const { return m_ht.equal_range(key); } + + /** + * @copydoc equal_range(const Key& key, std::size_t precalculated_hash) + */ + std::pair equal_range(const Key& key, std::size_t precalculated_hash) const { + return m_ht.equal_range(key, precalculated_hash); + } + + /** + * This overload only participates in the overload resolution if the typedef KeyEqual::is_transparent exists. + * If so, K must be hashable and comparable to Key. + */ + template::value>::type* = nullptr> + std::pair equal_range(const K& key) { return m_ht.equal_range(key); } + + /** + * @copydoc equal_range(const K& key) + * + * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same + * as hash_function()(key). Usefull to speed-up the lookup if you already have the hash. + */ + template::value>::type* = nullptr> + std::pair equal_range(const K& key, std::size_t precalculated_hash) { + return m_ht.equal_range(key, precalculated_hash); + } + + /** + * @copydoc equal_range(const K& key) + */ + template::value>::type* = nullptr> + std::pair equal_range(const K& key) const { return m_ht.equal_range(key); } + + /** + * @copydoc equal_range(const K& key, std::size_t precalculated_hash) + */ + template::value>::type* = nullptr> + std::pair equal_range(const K& key, std::size_t precalculated_hash) const { + return m_ht.equal_range(key, precalculated_hash); + } + + + + /* + * Bucket interface + */ + size_type bucket_count() const { return m_ht.bucket_count(); } + size_type max_bucket_count() const { return m_ht.max_bucket_count(); } + + + /* + * Hash policy + */ + float load_factor() const { return m_ht.load_factor(); } + float max_load_factor() const { return m_ht.max_load_factor(); } + void max_load_factor(float ml) { m_ht.max_load_factor(ml); } + + void rehash(size_type count) { m_ht.rehash(count); } + void reserve(size_type count) { m_ht.reserve(count); } + + + /* + * Observers + */ + hasher hash_function() const { return m_ht.hash_function(); } + key_equal key_eq() const { return m_ht.key_eq(); } + + + + /* + * Other + */ + + /** + * Convert a const_iterator to an iterator. + */ + iterator mutable_iterator(const_iterator pos) { + return m_ht.mutable_iterator(pos); + } + + /** + * Requires index <= size(). + * + * Return an iterator to the element at index. Return end() if index == size(). + */ + iterator nth(size_type index) { return m_ht.nth(index); } + + /** + * @copydoc nth(size_type index) + */ + const_iterator nth(size_type index) const { return m_ht.nth(index); } + + + /** + * Return const_reference to the first element. Requires the container to not be empty. + */ + const_reference front() const { return m_ht.front(); } + + /** + * Return const_reference to the last element. Requires the container to not be empty. + */ + const_reference back() const { return m_ht.back(); } + + + /** + * Only available if ValueTypeContainer is a std::vector. Same as calling 'values_container().data()'. + */ + template::value>::type* = nullptr> + const typename values_container_type::value_type* data() const noexcept { return m_ht.data(); } + + /** + * Return the container in which the values are stored. The values are in the same order as the insertion order + * and are contiguous in the structure, no holes (size() == values_container().size()). + */ + const values_container_type& values_container() const noexcept { return m_ht.values_container(); } + + template::value>::type* = nullptr> + size_type capacity() const noexcept { return m_ht.capacity(); } + + void shrink_to_fit() { m_ht.shrink_to_fit(); } + + + + /** + * Insert the value before pos shifting all the elements on the right of pos (including pos) one position + * to the right. + * + * Amortized linear time-complexity in the distance between pos and end(). + */ + std::pair insert_at_position(const_iterator pos, const value_type& value) { + return m_ht.insert_at_position(pos, value); + } + + /** + * @copydoc insert_at_position(const_iterator pos, const value_type& value) + */ + std::pair insert_at_position(const_iterator pos, value_type&& value) { + return m_ht.insert_at_position(pos, std::move(value)); + } + + /** + * @copydoc insert_at_position(const_iterator pos, const value_type& value) + * + * Same as insert_at_position(pos, value_type(std::forward(args)...), mainly + * here for coherence. + */ + template + std::pair emplace_at_position(const_iterator pos, Args&&... args) { + return m_ht.emplace_at_position(pos, std::forward(args)...); + } + + /** + * @copydoc insert_at_position(const_iterator pos, const value_type& value) + */ + template + std::pair try_emplace_at_position(const_iterator pos, const key_type& k, Args&&... args) { + return m_ht.try_emplace_at_position(pos, k, std::forward(args)...); + } + + /** + * @copydoc insert_at_position(const_iterator pos, const value_type& value) + */ + template + std::pair try_emplace_at_position(const_iterator pos, key_type&& k, Args&&... args) { + return m_ht.try_emplace_at_position(pos, std::move(k), std::forward(args)...); + } + + + + void pop_back() { m_ht.pop_back(); } + + /** + * Faster erase operation with an O(1) average complexity but it doesn't preserve the insertion order. + * + * If an erasure occurs, the last element of the map will take the place of the erased element. + */ + iterator unordered_erase(iterator pos) { return m_ht.unordered_erase(pos); } + + /** + * @copydoc unordered_erase(iterator pos) + */ + iterator unordered_erase(const_iterator pos) { return m_ht.unordered_erase(pos); } + + /** + * @copydoc unordered_erase(iterator pos) + */ + size_type unordered_erase(const key_type& key) { return m_ht.unordered_erase(key); } + + /** + * @copydoc unordered_erase(iterator pos) + * + * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same + * as hash_function()(key). Usefull to speed-up the lookup if you already have the hash. + */ + size_type unordered_erase(const key_type& key, std::size_t precalculated_hash) { + return m_ht.unordered_erase(key, precalculated_hash); + } + + /** + * @copydoc unordered_erase(iterator pos) + * + * This overload only participates in the overload resolution if the typedef KeyEqual::is_transparent exists. + * If so, K must be hashable and comparable to Key. + */ + template::value>::type* = nullptr> + size_type unordered_erase(const K& key) { return m_ht.unordered_erase(key); } + + /** + * @copydoc unordered_erase(const K& key) + * + * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same + * as hash_function()(key). Usefull to speed-up the lookup if you already have the hash. + */ + template::value>::type* = nullptr> + size_type unordered_erase(const K& key, std::size_t precalculated_hash) { + return m_ht.unordered_erase(key, precalculated_hash); + } + + /** + * Serialize the map through the `serializer` parameter. + * + * The `serializer` parameter must be a function object that supports the following call: + * - `template void operator()(const U& value);` where the types `std::uint64_t`, `float` and `std::pair` must be supported for U. + * + * The implementation leaves binary compatibilty (endianness, IEEE 754 for floats, ...) of the types it serializes + * in the hands of the `Serializer` function object if compatibilty is required. + */ + template + void serialize(Serializer& serializer) const { + m_ht.serialize(serializer); + } + + /** + * Deserialize a previouly serialized map through the `deserializer` parameter. + * + * The `deserializer` parameter must be a function object that supports the following calls: + * - `template U operator()();` where the types `std::uint64_t`, `float` and `std::pair` must be supported for U. + * + * If the deserialized hash map type is hash compatible with the serialized map, the deserialization process can be + * sped up by setting `hash_compatible` to true. To be hash compatible, the Hash and KeyEqual must behave the same way + * than the ones used on the serialized map. The `std::size_t` must also be of the same size as the one on the platform used + * to serialize the map, the same apply for `IndexType`. If these criteria are not met, the behaviour is undefined with + * `hash_compatible` sets to true. + * + * The behaviour is undefined if the type `Key` and `T` of the `ordered_map` are not the same as the + * types used during serialization. + * + * The implementation leaves binary compatibilty (endianness, IEEE 754 for floats, size of int, ...) of the types it + * deserializes in the hands of the `Deserializer` function object if compatibilty is required. + */ + template + static ordered_map deserialize(Deserializer& deserializer, bool hash_compatible = false) { + ordered_map map(0); + map.m_ht.deserialize(deserializer, hash_compatible); + + return map; + } + + + + friend bool operator==(const ordered_map& lhs, const ordered_map& rhs) { return lhs.m_ht == rhs.m_ht; } + friend bool operator!=(const ordered_map& lhs, const ordered_map& rhs) { return lhs.m_ht != rhs.m_ht; } + friend bool operator<(const ordered_map& lhs, const ordered_map& rhs) { return lhs.m_ht < rhs.m_ht; } + friend bool operator<=(const ordered_map& lhs, const ordered_map& rhs) { return lhs.m_ht <= rhs.m_ht; } + friend bool operator>(const ordered_map& lhs, const ordered_map& rhs) { return lhs.m_ht > rhs.m_ht; } + friend bool operator>=(const ordered_map& lhs, const ordered_map& rhs) { return lhs.m_ht >= rhs.m_ht; } + + friend void swap(ordered_map& lhs, ordered_map& rhs) { lhs.swap(rhs); } + +private: + ht m_ht; +}; + +} // end namespace tsl + +#endif diff --git a/src/tessil/ordered_set.h b/src/tessil/ordered_set.h new file mode 100644 index 0000000000..617cbbb57e --- /dev/null +++ b/src/tessil/ordered_set.h @@ -0,0 +1,688 @@ +/** + * MIT License + * + * Copyright (c) 2017 Tessil + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef TSL_ORDERED_SET_H +#define TSL_ORDERED_SET_H + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "ordered_hash.h" + + +namespace tsl { + + +/** + * Implementation of an hash set using open adressing with robin hood with backshift delete to resolve collisions. + * + * The particularity of this hash set is that it remembers the order in which the elements were added and + * provide a way to access the structure which stores these values through the 'values_container()' method. + * The used container is defined by ValueTypeContainer, by default a std::deque is used (grows faster) but + * a std::vector may be used. In this case the set provides a 'data()' method which give a direct access + * to the memory used to store the values (which can be usefull to communicate with C API's). + * + * The Key must be copy constructible and/or move constructible. To use `unordered_erase` it also must be swappable. + * + * The behaviour of the hash set is undefinded if the destructor of Key throws an exception. + * + * By default the maximum size of a set is limited to 2^32 - 1 values, if needed this can be changed through + * the IndexType template parameter. Using an `uint64_t` will raise this limit to 2^64 - 1 values but each + * bucket will use 16 bytes instead of 8 bytes in addition to the space needed to store the values. + * + * Iterators invalidation: + * - clear, operator=, reserve, rehash: always invalidate the iterators (also invalidate end()). + * - insert, emplace, emplace_hint, operator[]: when a std::vector is used as ValueTypeContainer + * and if size() < capacity(), only end(). + * Otherwise all the iterators are invalidated if an insert occurs. + * - erase, unordered_erase: when a std::vector is used as ValueTypeContainer invalidate the iterator of + * the erased element and all the ones after the erased element (including end()). + * Otherwise all the iterators are invalidated if an erase occurs. + */ +template, + class KeyEqual = std::equal_to, + class Allocator = std::allocator, + class ValueTypeContainer = std::vector, + class IndexType = std::uint_least32_t> +class ordered_set { +private: + template + using has_is_transparent = tsl::detail_ordered_hash::has_is_transparent; + + class KeySelect { + public: + using key_type = Key; + + const key_type& operator()(const Key& key) const noexcept { + return key; + } + + key_type& operator()(Key& key) noexcept { + return key; + } + }; + + using ht = detail_ordered_hash::ordered_hash; + +public: + using key_type = typename ht::key_type; + using value_type = typename ht::value_type; + using size_type = typename ht::size_type; + using difference_type = typename ht::difference_type; + using hasher = typename ht::hasher; + using key_equal = typename ht::key_equal; + using allocator_type = typename ht::allocator_type; + using reference = typename ht::reference; + using const_reference = typename ht::const_reference; + using pointer = typename ht::pointer; + using const_pointer = typename ht::const_pointer; + using iterator = typename ht::iterator; + using const_iterator = typename ht::const_iterator; + using reverse_iterator = typename ht::reverse_iterator; + using const_reverse_iterator = typename ht::const_reverse_iterator; + + using values_container_type = typename ht::values_container_type; + + + /* + * Constructors + */ + ordered_set(): ordered_set(ht::DEFAULT_INIT_BUCKETS_SIZE) { + } + + explicit ordered_set(size_type bucket_count, + const Hash& hash = Hash(), + const KeyEqual& equal = KeyEqual(), + const Allocator& alloc = Allocator()): + m_ht(bucket_count, hash, equal, alloc, ht::DEFAULT_MAX_LOAD_FACTOR) + { + } + + ordered_set(size_type bucket_count, + const Allocator& alloc): ordered_set(bucket_count, Hash(), KeyEqual(), alloc) + { + } + + ordered_set(size_type bucket_count, + const Hash& hash, + const Allocator& alloc): ordered_set(bucket_count, hash, KeyEqual(), alloc) + { + } + + explicit ordered_set(const Allocator& alloc): ordered_set(ht::DEFAULT_INIT_BUCKETS_SIZE, alloc) { + } + + template + ordered_set(InputIt first, InputIt last, + size_type bucket_count = ht::DEFAULT_INIT_BUCKETS_SIZE, + const Hash& hash = Hash(), + const KeyEqual& equal = KeyEqual(), + const Allocator& alloc = Allocator()): ordered_set(bucket_count, hash, equal, alloc) + { + insert(first, last); + } + + template + ordered_set(InputIt first, InputIt last, + size_type bucket_count, + const Allocator& alloc): ordered_set(first, last, bucket_count, Hash(), KeyEqual(), alloc) + { + } + + template + ordered_set(InputIt first, InputIt last, + size_type bucket_count, + const Hash& hash, + const Allocator& alloc): ordered_set(first, last, bucket_count, hash, KeyEqual(), alloc) + { + } + + ordered_set(std::initializer_list init, + size_type bucket_count = ht::DEFAULT_INIT_BUCKETS_SIZE, + const Hash& hash = Hash(), + const KeyEqual& equal = KeyEqual(), + const Allocator& alloc = Allocator()): + ordered_set(init.begin(), init.end(), bucket_count, hash, equal, alloc) + { + } + + ordered_set(std::initializer_list init, + size_type bucket_count, + const Allocator& alloc): + ordered_set(init.begin(), init.end(), bucket_count, Hash(), KeyEqual(), alloc) + { + } + + ordered_set(std::initializer_list init, + size_type bucket_count, + const Hash& hash, + const Allocator& alloc): + ordered_set(init.begin(), init.end(), bucket_count, hash, KeyEqual(), alloc) + { + } + + + ordered_set& operator=(std::initializer_list ilist) { + m_ht.clear(); + + m_ht.reserve(ilist.size()); + m_ht.insert(ilist.begin(), ilist.end()); + + return *this; + } + + allocator_type get_allocator() const { return m_ht.get_allocator(); } + + + /* + * Iterators + */ + iterator begin() noexcept { return m_ht.begin(); } + const_iterator begin() const noexcept { return m_ht.begin(); } + const_iterator cbegin() const noexcept { return m_ht.cbegin(); } + + iterator end() noexcept { return m_ht.end(); } + const_iterator end() const noexcept { return m_ht.end(); } + const_iterator cend() const noexcept { return m_ht.cend(); } + + reverse_iterator rbegin() noexcept { return m_ht.rbegin(); } + const_reverse_iterator rbegin() const noexcept { return m_ht.rbegin(); } + const_reverse_iterator rcbegin() const noexcept { return m_ht.rcbegin(); } + + reverse_iterator rend() noexcept { return m_ht.rend(); } + const_reverse_iterator rend() const noexcept { return m_ht.rend(); } + const_reverse_iterator rcend() const noexcept { return m_ht.rcend(); } + + + /* + * Capacity + */ + bool empty() const noexcept { return m_ht.empty(); } + size_type size() const noexcept { return m_ht.size(); } + size_type max_size() const noexcept { return m_ht.max_size(); } + + /* + * Modifiers + */ + void clear() noexcept { m_ht.clear(); } + + + + std::pair insert(const value_type& value) { return m_ht.insert(value); } + std::pair insert(value_type&& value) { return m_ht.insert(std::move(value)); } + + iterator insert(const_iterator hint, const value_type& value) { + return m_ht.insert_hint(hint, value); + } + + iterator insert(const_iterator hint, value_type&& value) { + return m_ht.insert_hint(hint, std::move(value)); + } + + template + void insert(InputIt first, InputIt last) { m_ht.insert(first, last); } + void insert(std::initializer_list ilist) { m_ht.insert(ilist.begin(), ilist.end()); } + + + + /** + * Due to the way elements are stored, emplace will need to move or copy the key-value once. + * The method is equivalent to insert(value_type(std::forward(args)...)); + * + * Mainly here for compatibility with the std::unordered_map interface. + */ + template + std::pair emplace(Args&&... args) { return m_ht.emplace(std::forward(args)...); } + + /** + * Due to the way elements are stored, emplace_hint will need to move or copy the key-value once. + * The method is equivalent to insert(hint, value_type(std::forward(args)...)); + * + * Mainly here for compatibility with the std::unordered_map interface. + */ + template + iterator emplace_hint(const_iterator hint, Args&&... args) { + return m_ht.emplace_hint(hint, std::forward(args)...); + } + + /** + * When erasing an element, the insert order will be preserved and no holes will be present in the container + * returned by 'values_container()'. + * + * The method is in O(n), if the order is not important 'unordered_erase(...)' method is faster with an O(1) + * average complexity. + */ + iterator erase(iterator pos) { return m_ht.erase(pos); } + + /** + * @copydoc erase(iterator pos) + */ + iterator erase(const_iterator pos) { return m_ht.erase(pos); } + + /** + * @copydoc erase(iterator pos) + */ + iterator erase(const_iterator first, const_iterator last) { return m_ht.erase(first, last); } + + /** + * @copydoc erase(iterator pos) + */ + size_type erase(const key_type& key) { return m_ht.erase(key); } + + /** + * @copydoc erase(iterator pos) + * + * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same + * as hash_function()(key). Usefull to speed-up the lookup to the value if you already have the hash. + */ + size_type erase(const key_type& key, std::size_t precalculated_hash) { + return m_ht.erase(key, precalculated_hash); + } + + /** + * @copydoc erase(iterator pos) + * + * This overload only participates in the overload resolution if the typedef KeyEqual::is_transparent exists. + * If so, K must be hashable and comparable to Key. + */ + template::value>::type* = nullptr> + size_type erase(const K& key) { return m_ht.erase(key); } + + /** + * @copydoc erase(const key_type& key, std::size_t precalculated_hash) + * + * This overload only participates in the overload resolution if the typedef KeyEqual::is_transparent exists. + * If so, K must be hashable and comparable to Key. + */ + template::value>::type* = nullptr> + size_type erase(const K& key, std::size_t precalculated_hash) { + return m_ht.erase(key, precalculated_hash); + } + + + + void swap(ordered_set& other) { other.m_ht.swap(m_ht); } + + /* + * Lookup + */ + size_type count(const Key& key) const { return m_ht.count(key); } + + /** + * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same + * as hash_function()(key). Usefull to speed-up the lookup if you already have the hash. + */ + size_type count(const Key& key, std::size_t precalculated_hash) const { + return m_ht.count(key, precalculated_hash); + } + + /** + * This overload only participates in the overload resolution if the typedef KeyEqual::is_transparent exists. + * If so, K must be hashable and comparable to Key. + */ + template::value>::type* = nullptr> + size_type count(const K& key) const { return m_ht.count(key); } + + /** + * @copydoc count(const K& key) const + * + * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same + * as hash_function()(key). Usefull to speed-up the lookup if you already have the hash. + */ + template::value>::type* = nullptr> + size_type count(const K& key, std::size_t precalculated_hash) const { + return m_ht.count(key, precalculated_hash); + } + + + + + iterator find(const Key& key) { return m_ht.find(key); } + + /** + * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same + * as hash_function()(key). Usefull to speed-up the lookup if you already have the hash. + */ + iterator find(const Key& key, std::size_t precalculated_hash) { return m_ht.find(key, precalculated_hash); } + + const_iterator find(const Key& key) const { return m_ht.find(key); } + + /** + * @copydoc find(const Key& key, std::size_t precalculated_hash) + */ + const_iterator find(const Key& key, std::size_t precalculated_hash) const { + return m_ht.find(key, precalculated_hash); + } + + /** + * This overload only participates in the overload resolution if the typedef KeyEqual::is_transparent exists. + * If so, K must be hashable and comparable to Key. + */ + template::value>::type* = nullptr> + iterator find(const K& key) { return m_ht.find(key); } + + /** + * @copydoc find(const K& key) + * + * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same + * as hash_function()(key). Usefull to speed-up the lookup if you already have the hash. + */ + template::value>::type* = nullptr> + iterator find(const K& key, std::size_t precalculated_hash) { return m_ht.find(key, precalculated_hash); } + + /** + * @copydoc find(const K& key) + */ + template::value>::type* = nullptr> + const_iterator find(const K& key) const { return m_ht.find(key); } + + /** + * @copydoc find(const K& key) + * + * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same + * as hash_function()(key). Usefull to speed-up the lookup if you already have the hash. + */ + template::value>::type* = nullptr> + const_iterator find(const K& key, std::size_t precalculated_hash) const { + return m_ht.find(key, precalculated_hash); + } + + + + std::pair equal_range(const Key& key) { return m_ht.equal_range(key); } + + /** + * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same + * as hash_function()(key). Usefull to speed-up the lookup if you already have the hash. + */ + std::pair equal_range(const Key& key, std::size_t precalculated_hash) { + return m_ht.equal_range(key, precalculated_hash); + } + + std::pair equal_range(const Key& key) const { return m_ht.equal_range(key); } + + /** + * @copydoc equal_range(const Key& key, std::size_t precalculated_hash) + */ + std::pair equal_range(const Key& key, std::size_t precalculated_hash) const { + return m_ht.equal_range(key, precalculated_hash); + } + + /** + * This overload only participates in the overload resolution if the typedef KeyEqual::is_transparent exists. + * If so, K must be hashable and comparable to Key. + */ + template::value>::type* = nullptr> + std::pair equal_range(const K& key) { return m_ht.equal_range(key); } + + /** + * @copydoc equal_range(const K& key) + * + * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same + * as hash_function()(key). Usefull to speed-up the lookup if you already have the hash. + */ + template::value>::type* = nullptr> + std::pair equal_range(const K& key, std::size_t precalculated_hash) { + return m_ht.equal_range(key, precalculated_hash); + } + + /** + * @copydoc equal_range(const K& key) + */ + template::value>::type* = nullptr> + std::pair equal_range(const K& key) const { return m_ht.equal_range(key); } + + /** + * @copydoc equal_range(const K& key, std::size_t precalculated_hash) + */ + template::value>::type* = nullptr> + std::pair equal_range(const K& key, std::size_t precalculated_hash) const { + return m_ht.equal_range(key, precalculated_hash); + } + + + /* + * Bucket interface + */ + size_type bucket_count() const { return m_ht.bucket_count(); } + size_type max_bucket_count() const { return m_ht.max_bucket_count(); } + + + /* + * Hash policy + */ + float load_factor() const { return m_ht.load_factor(); } + float max_load_factor() const { return m_ht.max_load_factor(); } + void max_load_factor(float ml) { m_ht.max_load_factor(ml); } + + void rehash(size_type count) { m_ht.rehash(count); } + void reserve(size_type count) { m_ht.reserve(count); } + + + /* + * Observers + */ + hasher hash_function() const { return m_ht.hash_function(); } + key_equal key_eq() const { return m_ht.key_eq(); } + + + /* + * Other + */ + + /** + * Convert a const_iterator to an iterator. + */ + iterator mutable_iterator(const_iterator pos) { + return m_ht.mutable_iterator(pos); + } + + /** + * Requires index <= size(). + * + * Return an iterator to the element at index. Return end() if index == size(). + */ + iterator nth(size_type index) { return m_ht.nth(index); } + + /** + * @copydoc nth(size_type index) + */ + const_iterator nth(size_type index) const { return m_ht.nth(index); } + + + /** + * Return const_reference to the first element. Requires the container to not be empty. + */ + const_reference front() const { return m_ht.front(); } + + /** + * Return const_reference to the last element. Requires the container to not be empty. + */ + const_reference back() const { return m_ht.back(); } + + + /** + * Only available if ValueTypeContainer is a std::vector. Same as calling 'values_container().data()'. + */ + template::value>::type* = nullptr> + const typename values_container_type::value_type* data() const noexcept { return m_ht.data(); } + + /** + * Return the container in which the values are stored. The values are in the same order as the insertion order + * and are contiguous in the structure, no holes (size() == values_container().size()). + */ + const values_container_type& values_container() const noexcept { return m_ht.values_container(); } + + template::value>::type* = nullptr> + size_type capacity() const noexcept { return m_ht.capacity(); } + + void shrink_to_fit() { m_ht.shrink_to_fit(); } + + + + /** + * Insert the value before pos shifting all the elements on the right of pos (including pos) one position + * to the right. + * + * Amortized linear time-complexity in the distance between pos and end(). + */ + std::pair insert_at_position(const_iterator pos, const value_type& value) { + return m_ht.insert_at_position(pos, value); + } + + /** + * @copydoc insert_at_position(const_iterator pos, const value_type& value) + */ + std::pair insert_at_position(const_iterator pos, value_type&& value) { + return m_ht.insert_at_position(pos, std::move(value)); + } + + /** + * @copydoc insert_at_position(const_iterator pos, const value_type& value) + * + * Same as insert_at_position(pos, value_type(std::forward(args)...), mainly + * here for coherence. + */ + template + std::pair emplace_at_position(const_iterator pos, Args&&... args) { + return m_ht.emplace_at_position(pos, std::forward(args)...); + } + + + + void pop_back() { m_ht.pop_back(); } + + /** + * Faster erase operation with an O(1) average complexity but it doesn't preserve the insertion order. + * + * If an erasure occurs, the last element of the map will take the place of the erased element. + */ + iterator unordered_erase(iterator pos) { return m_ht.unordered_erase(pos); } + + /** + * @copydoc unordered_erase(iterator pos) + */ + iterator unordered_erase(const_iterator pos) { return m_ht.unordered_erase(pos); } + + /** + * @copydoc unordered_erase(iterator pos) + */ + size_type unordered_erase(const key_type& key) { return m_ht.unordered_erase(key); } + + /** + * @copydoc unordered_erase(iterator pos) + * + * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same + * as hash_function()(key). Usefull to speed-up the lookup if you already have the hash. + */ + size_type unordered_erase(const key_type& key, std::size_t precalculated_hash) { + return m_ht.unordered_erase(key, precalculated_hash); + } + + /** + * @copydoc unordered_erase(iterator pos) + * + * This overload only participates in the overload resolution if the typedef KeyEqual::is_transparent exists. + * If so, K must be hashable and comparable to Key. + */ + template::value>::type* = nullptr> + size_type unordered_erase(const K& key) { return m_ht.unordered_erase(key); } + + /** + * @copydoc unordered_erase(const K& key) + * + * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same + * as hash_function()(key). Usefull to speed-up the lookup if you already have the hash. + */ + template::value>::type* = nullptr> + size_type unordered_erase(const K& key, std::size_t precalculated_hash) { + return m_ht.unordered_erase(key, precalculated_hash); + } + + /** + * Serialize the set through the `serializer` parameter. + * + * The `serializer` parameter must be a function object that supports the following call: + * - `void operator()(const U& value);` where the types `std::uint64_t`, `float` and `Key` must be supported for U. + * + * The implementation leaves binary compatibilty (endianness, IEEE 754 for floats, ...) of the types it serializes + * in the hands of the `Serializer` function object if compatibilty is required. + */ + template + void serialize(Serializer& serializer) const { + m_ht.serialize(serializer); + } + + /** + * Deserialize a previouly serialized set through the `deserializer` parameter. + * + * The `deserializer` parameter must be a function object that supports the following calls: + * - `template U operator()();` where the types `std::uint64_t`, `float` and `Key` must be supported for U. + * + * If the deserialized hash set type is hash compatible with the serialized set, the deserialization process can be + * sped up by setting `hash_compatible` to true. To be hash compatible, the Hash and KeyEqual must behave the same way + * than the ones used on the serialized map. The `std::size_t` must also be of the same size as the one on the platform used + * to serialize the map, the same apply for `IndexType`. If these criteria are not met, the behaviour is undefined with + * `hash_compatible` sets to true. + * + * The behaviour is undefined if the type `Key` of the `ordered_set` is not the same as the + * type used during serialization. + * + * The implementation leaves binary compatibilty (endianness, IEEE 754 for floats, size of int, ...) of the types it + * deserializes in the hands of the `Deserializer` function object if compatibilty is required. + */ + template + static ordered_set deserialize(Deserializer& deserializer, bool hash_compatible = false) { + ordered_set set(0); + set.m_ht.deserialize(deserializer, hash_compatible); + + return set; + } + + + + friend bool operator==(const ordered_set& lhs, const ordered_set& rhs) { return lhs.m_ht == rhs.m_ht; } + friend bool operator!=(const ordered_set& lhs, const ordered_set& rhs) { return lhs.m_ht != rhs.m_ht; } + friend bool operator<(const ordered_set& lhs, const ordered_set& rhs) { return lhs.m_ht < rhs.m_ht; } + friend bool operator<=(const ordered_set& lhs, const ordered_set& rhs) { return lhs.m_ht <= rhs.m_ht; } + friend bool operator>(const ordered_set& lhs, const ordered_set& rhs) { return lhs.m_ht > rhs.m_ht; } + friend bool operator>=(const ordered_set& lhs, const ordered_set& rhs) { return lhs.m_ht >= rhs.m_ht; } + + friend void swap(ordered_set& lhs, ordered_set& rhs) { lhs.swap(rhs); } + +private: + ht m_ht; +}; + +} // end namespace tsl + +#endif diff --git a/src/to_value.cpp b/src/to_value.cpp deleted file mode 100644 index fa2b174d97..0000000000 --- a/src/to_value.cpp +++ /dev/null @@ -1,114 +0,0 @@ -// sass.hpp must go before all system headers to get the -// __EXTENSIONS__ fix on Solaris. -#include "sass.hpp" - -#include "ast.hpp" -#include "to_value.hpp" - -namespace Sass { - - // Custom_Error is a valid value - Value* To_Value::operator()(Custom_Error* e) - { - return e; - } - - // Custom_Warning is a valid value - Value* To_Value::operator()(Custom_Warning* w) - { - return w; - } - - // Boolean is a valid value - Value* To_Value::operator()(Boolean* b) - { - return b; - } - - // Number is a valid value - Value* To_Value::operator()(Number* n) - { - return n; - } - - // Color is a valid value - Value* To_Value::operator()(Color_RGBA* c) - { - return c; - } - - // Color is a valid value - Value* To_Value::operator()(Color_HSLA* c) - { - return c; - } - - // String_Constant is a valid value - Value* To_Value::operator()(String_Constant* s) - { - return s; - } - - // String_Quoted is a valid value - Value* To_Value::operator()(String_Quoted* s) - { - return s; - } - - // List is a valid value - Value* To_Value::operator()(List* l) - { - List_Obj ll = SASS_MEMORY_NEW(List, - l->pstate(), - l->length(), - l->separator(), - l->is_arglist(), - l->is_bracketed()); - for (size_t i = 0, L = l->length(); i < L; ++i) { - ll->append((*l)[i]->perform(this)); - } - return ll.detach(); - } - - // Map is a valid value - Value* To_Value::operator()(Map* m) - { - return m; - } - - // Null is a valid value - Value* To_Value::operator()(Null* n) - { - return n; - } - - // Function is a valid value - Value* To_Value::operator()(Function* n) - { - return n; - } - - // Argument returns its value - Value* To_Value::operator()(Argument* arg) - { - if (!arg->name().empty()) return 0; - return arg->value()->perform(this); - } - - // SelectorList is converted to a string - Value* To_Value::operator()(SelectorList* s) - { - return SASS_MEMORY_NEW(String_Quoted, - s->pstate(), - s->to_string(ctx.c_options)); - } - - // Binary_Expression is converted to a string - Value* To_Value::operator()(Binary_Expression* s) - { - return SASS_MEMORY_NEW(String_Quoted, - s->pstate(), - s->to_string(ctx.c_options)); - } - -}; diff --git a/src/to_value.hpp b/src/to_value.hpp deleted file mode 100644 index 736e17defb..0000000000 --- a/src/to_value.hpp +++ /dev/null @@ -1,46 +0,0 @@ -#ifndef SASS_TO_VALUE_H -#define SASS_TO_VALUE_H - -#include "operation.hpp" -#include "sass/values.h" -#include "ast_fwd_decl.hpp" - -namespace Sass { - - class To_Value : public Operation_CRTP { - - private: - - Context& ctx; - - public: - - To_Value(Context& ctx) - : ctx(ctx) - { } - ~To_Value() { } - using Operation::operator(); - - Value* operator()(Argument*); - Value* operator()(Boolean*); - Value* operator()(Number*); - Value* operator()(Color_RGBA*); - Value* operator()(Color_HSLA*); - Value* operator()(String_Constant*); - Value* operator()(String_Quoted*); - Value* operator()(Custom_Warning*); - Value* operator()(Custom_Error*); - Value* operator()(List*); - Value* operator()(Map*); - Value* operator()(Null*); - Value* operator()(Function*); - - // convert to string via `To_String` - Value* operator()(SelectorList*); - Value* operator()(Binary_Expression*); - - }; - -} - -#endif diff --git a/src/unicode.cpp b/src/unicode.cpp new file mode 100644 index 0000000000..0d15d444fb --- /dev/null +++ b/src/unicode.cpp @@ -0,0 +1,111 @@ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#include "unicode.hpp" + +namespace Sass { + namespace Unicode { + + // naming conventions: + // bytes: raw byte offset (0 based) + // position: code point offset (0 based) + + // Return number of code points in utf8 string + size_t codePointCount(const sass::string& utf8) { + return utf8::distance(utf8.begin(), utf8.end()); + } + // EO codePointCount + + // Return number of code points in utf8 string up to bytes offset. + size_t codePointCount(const sass::string& utf8, size_t bytes) { + return utf8::distance(utf8.begin(), utf8.begin() + bytes); + } + // EO codePointCount + + // Return the byte offset at a code point position + size_t byteOffsetAtPosition(const sass::string& utf8, size_t position) { + sass::string::const_iterator it = utf8.begin(); + utf8::advance(it, position, utf8.end()); + return std::distance(utf8.begin(), it); + } + // EO byteOffsetAtPosition + + // Returns utf8 aware substring. + // Parameters are in code points. + sass::string substr( + sass::string& utf8, + size_t start, + size_t len) + { + auto first = utf8.begin(); + utf8::advance(first, + start, utf8.end()); + auto last = first; + if (len != sass::string::npos) { + utf8::advance(last, + len, utf8.end()); + } + else { + last = utf8.end(); + } + return sass::string( + first, last); + } + // EO substr + + // Utf8 aware string replacement. + // Parameters are in code points. + // Inserted text must be valid utf8. + sass::string replace( + sass::string& text, + size_t start, size_t len, + const sass::string& insert) + { + auto first = text.begin(); + utf8::advance(first, + start, text.end()); + auto last = first; + if (len != sass::string::npos) { + utf8::advance(last, + len, text.end()); + } + else { + last = text.end(); + } + return text.replace( + first, last, + insert); + } + // EO replace + + #ifdef _WIN32 + + // utf16 functions + using std::wstring; + + // convert from utf16/wide string to utf8 string + sass::string utf16to8(const sass::wstring& utf16) + { + sass::string utf8; + // preallocate expected memory + utf8.reserve(sizeof(utf16)/2); + utf8::utf16to8(utf16.begin(), utf16.end(), + back_inserter(utf8)); + return utf8; + } + + // convert from utf8 string to utf16/wide string + sass::wstring utf8to16(const sass::string& utf8) + { + sass::wstring utf16; + // preallocate expected memory + utf16.reserve(codePointCount(utf8)*2); + utf8::utf8to16(utf8.begin(), utf8.end(), + back_inserter(utf16)); + return utf16; + } + + #endif + + } +} diff --git a/src/unicode.hpp b/src/unicode.hpp new file mode 100644 index 0000000000..bb3fe20251 --- /dev/null +++ b/src/unicode.hpp @@ -0,0 +1,48 @@ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#ifndef SASS_UNICODE_HPP +#define SASS_UNICODE_HPP + +// sass.hpp must go before all system headers +// to get the __EXTENSIONS__ fix on Solaris. +#include "capi_sass.hpp" + +#include "utf8/checked.h" +#include "utf8/unchecked.h" + +namespace Sass { + namespace Unicode { + + // naming conventions: + // bytes: raw byte offset (0 based) + // position: code point offset (0 based) + + // Return number of code points in utf8 string up to bytes offset. + size_t codePointCount(const sass::string& utf8, size_t bytes); + + // Return number of code points in utf8 string + size_t codePointCount(const sass::string& utf8); + + // function that will return the byte offset of a code point in a + size_t byteOffsetAtPosition(const sass::string& utf8, size_t position); + + // Returns utf8 aware substring. + // Parameters are in code points. + sass::string substr(sass::string& utf8, size_t start, size_t len); + + // Utf8 aware string replacement. + // Parameters are in code points. + // Inserted text must be valid utf8. + sass::string replace(sass::string& utf8, size_t start, size_t len, const sass::string& insert); + + #ifdef _WIN32 + // functions to handle unicode paths on windows + sass::string utf16to8(const sass::wstring& utf16); + sass::wstring utf8to16(const sass::string& utf8); + #endif + + } +} + +#endif diff --git a/src/units.cpp b/src/units.cpp index bce92f9905..1d85cf4774 100644 --- a/src/units.cpp +++ b/src/units.cpp @@ -1,58 +1,75 @@ -#include "sass.hpp" -#include -#include -#include +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ #include "units.hpp" -#include "error_handling.hpp" + +#include "flat_map.hpp" +#include "string_utils.hpp" + +#include namespace Sass { - /* the conversion matrix can be readed the following way */ + ///////////////////////////////////////////////////////////////////////// + /* the conversion matrix can be read the following way */ /* if you go down, the factor is for the numerator (multiply) */ /* if you go right, the factor is for the denominator (divide) */ /* and yes, we actually use both, not sure why, but why not!? */ + ///////////////////////////////////////////////////////////////////////// - const double size_conversion_factors[6][6] = + const int size_conversion_factors_count = 7; + const double size_conversion_factors[7][7] = { - /* in cm pc mm pt px */ - /* in */ { 1, 2.54, 6, 25.4, 72, 96, }, - /* cm */ { 1.0/2.54, 1, 6.0/2.54, 10, 72.0/2.54, 96.0/2.54 }, - /* pc */ { 1.0/6.0, 2.54/6.0, 1, 25.4/6.0, 72.0/6.0, 96.0/6.0 }, - /* mm */ { 1.0/25.4, 1.0/10.0, 6.0/25.4, 1, 72.0/25.4, 96.0/25.4 }, - /* pt */ { 1.0/72.0, 2.54/72.0, 6.0/72.0, 25.4/72.0, 1, 96.0/72.0 }, - /* px */ { 1.0/96.0, 2.54/96.0, 6.0/96.0, 25.4/96.0, 72.0/96.0, 1, } + /* in cm pc mm pt px q */ + /* in */ { 1.0, 2.54, 6.0, 25.4, 72.0, 96.0, 101.6 }, + /* cm */ { 1.0/2.54, 2.54/2.54, 6.0/2.54, 25.4/2.54, 72.0/2.54, 96.0/2.54, 101.6/2.54 }, + /* pc */ { 1.0/6.0, 2.54/6.0, 6.0/6.0, 25.4/6.0, 72.0/6.0, 96.0/6.0, 101.6/6.0 }, + /* mm */ { 1.0/25.4, 2.54/25.4, 6.0/25.4, 25.4/25.4, 72.0/25.4, 96.0/25.4, 101.6/25.4 }, + /* pt */ { 1.0/72.0, 2.54/72.0, 6.0/72.0, 25.4/72.0, 72.0/72.0, 96.0/72.0, 101.6/72.0 }, + /* px */ { 1.0/96.0, 2.54/96.0, 6.0/96.0, 25.4/96.0, 72.0/96.0, 96.0/96.0, 101.6/96.0 }, + /* q */ { 1.0/101.6, 2.54/101.6, 6.0/101.6, 25.4/101.6, 72.0/101.6, 96.0/101.6, 101.6/101.6 } }; - const double angle_conversion_factors[4][4] = + const int time_conversion_factors_count = 2; + const double time_conversion_factors[2][2] = { - /* deg grad rad turn */ - /* deg */ { 1, 40.0/36.0, PI/180.0, 1.0/360.0 }, - /* grad */ { 36.0/40.0, 1, PI/200.0, 1.0/400.0 }, - /* rad */ { 180.0/PI, 200.0/PI, 1, 0.5/PI }, - /* turn */ { 360.0, 400.0, 2.0*PI, 1 } + /* s ms */ + /* s */ { 1.0, 1000.0 }, + /* ms */ { 1/1000.0, 1.0 } }; - const double time_conversion_factors[2][2] = + const int angle_conversion_factors_count = 4; + const double angle_conversion_factors[4][4] = { - /* s ms */ - /* s */ { 1, 1000.0 }, - /* ms */ { 1/1000.0, 1 } + /* deg grad rad turn */ + /* deg */ { 1.0, 40.0/36.0, PI/180.0, 1.0/360.0 }, + /* grad */ { 36.0/40.0, 1.0, PI/200.0, 1.0/400.0 }, + /* rad */ { 180.0/PI, 200.0/PI, 1.0, 0.5/PI }, + /* turn */ { 360.0, 400.0, 2.0*PI, 1.0 } }; + + const int frequency_conversion_factors_count = 2; const double frequency_conversion_factors[2][2] = { - /* Hz kHz */ - /* Hz */ { 1, 1/1000.0 }, - /* kHz */ { 1000.0, 1 } + /* Hz kHz */ + /* Hz */ { 1.0, 1/1000.0 }, + /* kHz */ { 1000.0, 1.0 } }; + + const int resolution_conversion_factors_count = 3; const double resolution_conversion_factors[3][3] = { - /* dpi dpcm dppx */ - /* dpi */ { 1, 1/2.54, 1/96.0 }, - /* dpcm */ { 2.54, 1, 2.54/96 }, - /* dppx */ { 96, 96/2.54, 1 } + /* dpi dpcm dppx */ + /* dpi */ { 1.0, 1.0/2.54, 1.0/96.0 }, + /* dpcm */ { 2.54, 1.0, 2.54/96 }, + /* dppx */ { 96.0, 96.0/2.54, 1.0 } }; - UnitClass get_unit_type(UnitType unit) + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // Return unit class enum for given unit type enum + UnitClass get_unit_class(UnitType unit) { switch (unit & 0xFF00) { @@ -65,60 +82,51 @@ namespace Sass { } }; - sass::string get_unit_class(UnitType unit) - { - switch (unit & 0xFF00) - { - case UnitClass::LENGTH: return "LENGTH"; - case UnitClass::ANGLE: return "ANGLE"; - case UnitClass::TIME: return "TIME"; - case UnitClass::FREQUENCY: return "FREQUENCY"; - case UnitClass::RESOLUTION: return "RESOLUTION"; - default: return "INCOMMENSURABLE"; - } - }; - - UnitType get_main_unit(const UnitClass unit) + // Return standard unit for the given unit class enum + UnitType get_standard_unit(const UnitClass unit) { switch (unit) { case UnitClass::LENGTH: return UnitType::PX; - case UnitClass::ANGLE: return UnitType::DEG; case UnitClass::TIME: return UnitType::SEC; + case UnitClass::ANGLE: return UnitType::DEG; case UnitClass::FREQUENCY: return UnitType::HERTZ; case UnitClass::RESOLUTION: return UnitType::DPI; default: return UnitType::UNKNOWN; } }; + // Return unit type enum from unit string UnitType string_to_unit(const sass::string& s) { // size units - if (s == "px") return UnitType::PX; - else if (s == "pt") return UnitType::PT; - else if (s == "pc") return UnitType::PC; - else if (s == "mm") return UnitType::MM; - else if (s == "cm") return UnitType::CM; - else if (s == "in") return UnitType::IN; - // angle units - else if (s == "deg") return UnitType::DEG; - else if (s == "grad") return UnitType::GRAD; - else if (s == "rad") return UnitType::RAD; - else if (s == "turn") return UnitType::TURN; + if (StringUtils::equalsIgnoreCase(s, "px", 2)) return UnitType::PX; + else if (StringUtils::equalsIgnoreCase(s, "pt", 2)) return UnitType::PT; + else if (StringUtils::equalsIgnoreCase(s, "pc", 2)) return UnitType::PC; + else if (StringUtils::equalsIgnoreCase(s, "mm", 2)) return UnitType::MM; + else if (StringUtils::equalsIgnoreCase(s, "cm", 2)) return UnitType::CM; + else if (StringUtils::equalsIgnoreCase(s, "in", 2)) return UnitType::INCH; + else if (StringUtils::equalsIgnoreCase(s, "q", 1)) return UnitType::QMM; // time units - else if (s == "s") return UnitType::SEC; - else if (s == "ms") return UnitType::MSEC; + else if (StringUtils::equalsIgnoreCase(s, "s", 1)) return UnitType::SEC; + else if (StringUtils::equalsIgnoreCase(s, "ms", 2)) return UnitType::MSEC; + // angle units + else if (StringUtils::equalsIgnoreCase(s, "deg", 3)) return UnitType::DEG; + else if (StringUtils::equalsIgnoreCase(s, "grad", 4)) return UnitType::GRAD; + else if (StringUtils::equalsIgnoreCase(s, "rad", 3)) return UnitType::RAD; + else if (StringUtils::equalsIgnoreCase(s, "turn", 4)) return UnitType::TURN; // frequency units - else if (s == "Hz") return UnitType::HERTZ; - else if (s == "kHz") return UnitType::KHERTZ; + else if (StringUtils::equalsIgnoreCase(s, "hz", 2)) return UnitType::HERTZ; + else if (StringUtils::equalsIgnoreCase(s, "khz", 3)) return UnitType::KHERTZ; // resolutions units - else if (s == "dpi") return UnitType::DPI; - else if (s == "dpcm") return UnitType::DPCM; - else if (s == "dppx") return UnitType::DPPX; + else if (StringUtils::equalsIgnoreCase(s, "dpi", 3)) return UnitType::DPI; + else if (StringUtils::equalsIgnoreCase(s, "dpcm", 4)) return UnitType::DPCM; + else if (StringUtils::equalsIgnoreCase(s, "dppx", 4)) return UnitType::DPPX; // for unknown units else return UnitType::UNKNOWN; } + // Return unit as string from unit type enum const char* unit_to_string(UnitType unit) { switch (unit) { @@ -128,15 +136,16 @@ namespace Sass { case UnitType::PC: return "pc"; case UnitType::MM: return "mm"; case UnitType::CM: return "cm"; - case UnitType::IN: return "in"; + case UnitType::INCH: return "in"; + case UnitType::QMM: return "q"; + // time units + case UnitType::SEC: return "s"; + case UnitType::MSEC: return "ms"; // angle units case UnitType::DEG: return "deg"; case UnitType::GRAD: return "grad"; case UnitType::RAD: return "rad"; case UnitType::TURN: return "turn"; - // time units - case UnitType::SEC: return "s"; - case UnitType::MSEC: return "ms"; // frequency units case UnitType::HERTZ: return "Hz"; case UnitType::KHERTZ: return "kHz"; @@ -149,34 +158,10 @@ namespace Sass { } } - sass::string unit_to_class(const sass::string& s) - { - if (s == "px") return "LENGTH"; - else if (s == "pt") return "LENGTH"; - else if (s == "pc") return "LENGTH"; - else if (s == "mm") return "LENGTH"; - else if (s == "cm") return "LENGTH"; - else if (s == "in") return "LENGTH"; - // angle units - else if (s == "deg") return "ANGLE"; - else if (s == "grad") return "ANGLE"; - else if (s == "rad") return "ANGLE"; - else if (s == "turn") return "ANGLE"; - // time units - else if (s == "s") return "TIME"; - else if (s == "ms") return "TIME"; - // frequency units - else if (s == "Hz") return "FREQUENCY"; - else if (s == "kHz") return "FREQUENCY"; - // resolutions units - else if (s == "dpi") return "RESOLUTION"; - else if (s == "dpcm") return "RESOLUTION"; - else if (s == "dppx") return "RESOLUTION"; - // for unknown units - return "CUSTOM:" + s; - } + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// - // throws incompatibleUnits exceptions + // Return conversion factor from s1 to s2 (returns zero for incompatible units) double conversion_factor(const sass::string& s1, const sass::string& s2) { // assert for same units @@ -185,64 +170,77 @@ namespace Sass { UnitType u1 = string_to_unit(s1); UnitType u2 = string_to_unit(s2); // query unit group types - UnitClass t1 = get_unit_type(u1); - UnitClass t2 = get_unit_type(u2); + UnitClass t1 = get_unit_class(u1); + UnitClass t2 = get_unit_class(u2); // return the conversion factor return conversion_factor(u1, u2, t1, t2); } - // throws incompatibleUnits exceptions + // Return conversion factor from u1 to u2 (returns zero for incompatible units) + // Note: unit classes are passed as parameters since we mostly already have them + // Note: not sure how much performance this saves, but it fits our use-cases well double conversion_factor(UnitType u1, UnitType u2, UnitClass t1, UnitClass t2) { - // can't convert between groups + // can't convert different classes if (t1 != t2) return 0; - // get absolute offset - // used for array acces - size_t i1 = u1 - t1; - size_t i2 = u2 - t2; + // get absolute offset for array access + size_t i1 = u1 & 0x00FF; + size_t i2 = u2 & 0x00FF; // process known units switch (t1) { case LENGTH: + if (i1 >= size_conversion_factors_count) return 0.0; + if (i2 >= size_conversion_factors_count) return 0.0; return size_conversion_factors[i1][i2]; - case ANGLE: - return angle_conversion_factors[i1][i2]; case TIME: + if (i1 >= time_conversion_factors_count) return 0.0; + if (i2 >= time_conversion_factors_count) return 0.0; return time_conversion_factors[i1][i2]; + case ANGLE: + if (i1 >= angle_conversion_factors_count) return 0.0; + if (i2 >= angle_conversion_factors_count) return 0.0; + return angle_conversion_factors[i1][i2]; case FREQUENCY: + if (i1 >= frequency_conversion_factors_count) return 0.0; + if (i2 >= frequency_conversion_factors_count) return 0.0; return frequency_conversion_factors[i1][i2]; case RESOLUTION: + if (i1 >= resolution_conversion_factors_count) return 0.0; + if (i2 >= resolution_conversion_factors_count) return 0.0; return resolution_conversion_factors[i1][i2]; case INCOMMENSURABLE: return 0; } - // fallback + // fall-back return 0; } - double convert_units(const sass::string& lhs, const sass::string& rhs, int& lhsexp, int& rhsexp) + // Reduce units so that the result either is fully represented by lhs or rhs unit. + // Exponents are adjusted accordingly and returning factor must be applied to the scalar. + double reduce_units(const sass::string& lhs, const sass::string& rhs, int& lhsexp, int& rhsexp) { double f = 0; // do not convert same ones - if (lhs == rhs) return 0; + if (lhs == rhs) return 0.0; // skip already canceled out unit - if (lhsexp == 0) return 0; - if (rhsexp == 0) return 0; + if (lhsexp == 0) return 0.0; + if (rhsexp == 0) return 0.0; // check if it can be converted UnitType ulhs = string_to_unit(lhs); UnitType urhs = string_to_unit(rhs); // skip units we cannot convert - if (ulhs == UNKNOWN) return 0; - if (urhs == UNKNOWN) return 0; + if (ulhs == UNKNOWN) return 0.0; + if (urhs == UNKNOWN) return 0.0; // query unit group types - UnitClass clhs = get_unit_type(ulhs); - UnitClass crhs = get_unit_type(urhs); + UnitClass clhs = get_unit_class(ulhs); + UnitClass crhs = get_unit_class(urhs); // skip units we cannot convert - if (clhs != crhs) return 0; + if (clhs != crhs) return 0.0; // if right denominator is bigger than lhs, we want to keep it in rhs unit if (rhsexp < 0 && lhsexp > 0 && - rhsexp > lhsexp) { // get the conversion factor for units f = conversion_factor(urhs, ulhs, clhs, crhs); - // left hand side has been consumned + // left hand side has been consumed f = std::pow(f, lhsexp); rhsexp += lhsexp; lhsexp = 0; @@ -250,7 +248,7 @@ namespace Sass { else { // get the conversion factor for units f = conversion_factor(ulhs, urhs, clhs, crhs); - // right hand side has been consumned + // right hand side has been consumed f = std::pow(f, rhsexp); lhsexp += rhsexp; rhsexp = 0; @@ -258,36 +256,38 @@ namespace Sass { return f; } - bool Units::operator< (const Units& rhs) const - { - return (numerators < rhs.numerators) && - (denominators < rhs.denominators); - } + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // Compare units (without any normalizing) bool Units::operator== (const Units& rhs) const { return (numerators == rhs.numerators) && (denominators == rhs.denominators); } - bool Units::operator!= (const Units& rhs) const - { - return ! (*this == rhs); - } + // Normalize all units to the standard unit class + // Additionally sorts all units in ascending order + // In combination with `reduce` this allows numbers + // to be compared for equality independent of units + // E.g. '1000ms' will be normalized to '1s' + // Returns factor to be applied to scalar double Units::normalize() { + stringified.clear(); size_t iL = numerators.size(); size_t nL = denominators.size(); // the final conversion factor - double factor = 1; + double factor = 1.0; for (size_t i = 0; i < iL; i++) { sass::string &lhs = numerators[i]; UnitType ulhs = string_to_unit(lhs); if (ulhs == UNKNOWN) continue; - UnitClass clhs = get_unit_type(ulhs); - UnitType umain = get_main_unit(clhs); + UnitClass clhs = get_unit_class(ulhs); + UnitType umain = get_standard_unit(clhs); if (ulhs == umain) continue; double f(conversion_factor(umain, ulhs, clhs, clhs)); if (f == 0) throw std::runtime_error("INVALID"); @@ -299,9 +299,10 @@ namespace Sass { sass::string &rhs = denominators[n]; UnitType urhs = string_to_unit(rhs); if (urhs == UNKNOWN) continue; - UnitClass crhs = get_unit_type(urhs); - UnitType umain = get_main_unit(crhs); + UnitClass crhs = get_unit_class(urhs); + UnitType umain = get_standard_unit(crhs); if (urhs == umain) continue; + // this is never hit via spec-tests!? double f(conversion_factor(umain, urhs, crhs, crhs)); if (f == 0) throw std::runtime_error("INVALID"); denominators[n] = unit_to_string(umain); @@ -314,10 +315,15 @@ namespace Sass { // return for conversion return factor; } + // EO normalize + // Cancel out all compatible unit classes + // E.g. `1000ms/s` will be reduced to `1` + // Returns factor to be applied to scalar double Units::reduce() { + stringified.clear(); size_t iL = numerators.size(); size_t nL = denominators.size(); @@ -327,8 +333,9 @@ namespace Sass { // first make sure same units cancel each other out // it seems that a map table will fit nicely to do this // we basically construct exponents for each unit - // has the advantage that they will be pre-sorted - std::map exponents; + // has the advantage that they will be presorted + // ToDo: use fast map implementation? + FlatMap exponents; // initialize by summing up occurrences in unit vectors // this will already cancel out equivalent units (e.q. px/px) @@ -343,7 +350,7 @@ namespace Sass { for (size_t n = 0; n < nL; n++) { sass::string &lhs = numerators[i], &rhs = denominators[n]; int &lhsexp = exponents[lhs], &rhsexp = exponents[rhs]; - double f(convert_units(lhs, rhs, lhsexp, rhsexp)); + double f(reduce_units(lhs, rhs, lhsexp, rhsexp)); if (f == 0) continue; factor /= f; } @@ -354,50 +361,260 @@ namespace Sass { denominators.clear(); // recreate sorted units vectors - for (auto exp : exponents) { - int &exponent = exp.second; + for (auto kv : exponents) { + int &exponent = kv.second; while (exponent > 0 && exponent --) - numerators.push_back(exp.first); + numerators.emplace_back(kv.first); while (exponent < 0 && exponent ++) - denominators.push_back(exp.first); + denominators.emplace_back(kv.first); } // return for conversion return factor; } + // EO reduce - sass::string Units::unit() const + // Reset unit without conversion factor + void Units::unit(const sass::string& u) { - sass::string u; - size_t iL = numerators.size(); - size_t nL = denominators.size(); - for (size_t i = 0; i < iL; i += 1) { - if (i) u += '*'; - u += numerators[i]; + size_t l = 0; + size_t r; + stringified.clear(); + numerators.clear(); + denominators.clear(); + if (!u.empty()) { + bool nominator = true; + while (true) { + r = u.find_first_of("*/", l); + sass::string unit(u.substr(l, r == sass::string::npos ? r : r - l)); + if (!unit.empty()) { + if (nominator) numerators.emplace_back(unit); + else denominators.emplace_back(unit); + } + if (r == sass::string::npos) break; + // ToDo: should error for multiple slashes + // if (!nominator && u[r] == '/') error(...) + if (u[r] == '/') + nominator = false; + // strange math parsing? + // else if (u[r] == '*') + // nominator = true; + l = r + 1; + } } - if (nL != 0) u += '/'; - for (size_t n = 0; n < nL; n += 1) { - if (n) u += '*'; - u += denominators[n]; + } + // EO unit + + // Convert units to string + const sass::string& Units::unit() const + { + // Units are expected to be short, so we hopefully + // can profit from small objects optimization. This + // is not guaranteed, but still safe to assume that + // any mature implementation utilizes it. + if (stringified.empty()) { + size_t iL = numerators.size(); + size_t nL = denominators.size(); + for (size_t i = 0; i < iL; i += 1) { + if (i) stringified += '*'; + stringified += numerators[i]; + } + if (iL == 0) { + if (nL > 1) stringified += '('; + for (size_t n = 0; n < nL; n += 1) { + if (n) stringified += '*'; + stringified += denominators[n]; + } + if (nL > 1) stringified += ')'; + if (nL != 0) stringified += "^-1"; + } + else { + if (nL != 0) stringified += '/'; + for (size_t n = 0; n < nL; n += 1) { + if (n) stringified += '*'; + stringified += denominators[n]; + } + } + } + /* + + if (stringified.empty()) { + size_t iL = numerators.size(); + size_t nL = denominators.size(); + if (iL > 0) { + stringified += numerators[0]; + } + for (size_t i = 1; i < iL; i += 1) { + stringified += " * 1"; + stringified += numerators[i]; + } + for (size_t n = 0; n < nL; n += 1) { + stringified += " / 1"; + stringified += denominators[n]; + } } - return u; + + */ + return stringified; + } + // EO unit + + // Convert units to string + const sass::string& Units::unit2() const + { + if (stringified.empty()) { + size_t iL = numerators.size(); + size_t nL = denominators.size(); + if (iL > 0) { + stringified += numerators[0]; + } + for (size_t i = 1; i < iL; i += 1) { + stringified += " * 1"; + stringified += numerators[i]; + } + for (size_t n = 0; n < nL; n += 1) { + stringified += " / 1"; + stringified += denominators[n]; + } + } + + return stringified; } + // EO unit + // Returns true if we only have given numerator + bool Units::isOnlyOfUnit(sass::string unit) const + { + return numerators.size() == 1 && + denominators.empty() && + numerators[0] == unit; + } + // EO isOnlyOfUnit - bool Units::is_unitless() const + // Returns true if empty + bool Units::isUnitless() const { return numerators.empty() && denominators.empty(); } + // EO isUnitless - bool Units::is_valid_css_unit() const + // Returns true if valid for css + bool Units::isValidCssUnit() const { return numerators.size() <= 1 && denominators.size() == 0; } + // EO isValidCssUnit + + const std::set KnownUnits({ + "em", "rem", "ex", "rex", "cap", "rcap", "ch", "rch", "ic", "ric", "lh", // + "rlh", "vw", "lvw", "svw", "dvw", "vh", "lvh", "svh", "dvh", "vi", "lvi", // + "svi", "dvi", "vb", "lvb", "svb", "dvb", "vmin", "lvmin", "svmin", // + "dvmin", "vmax", "lvmax", "svmax", "dvmax", "cqw", "cqh", "cqi", "cqb", // + "cqmin", "cqmax", "cm", "mm", "q", "in", "pt", "pc", "px", + "deg", "grad", "rad", "turn", + "dpi", "dpcm", "dppx", + "hz", "khz", + "s", "ms", + }); + + const std::set CompatUnitsLen({ + "em", "rem", "ex", "rex", "cap", "rcap", "ch", "rch", "ic", "ric", "lh", // + "rlh", "vw", "lvw", "svw", "dvw", "vh", "lvh", "svh", "dvh", "vi", "lvi", // + "svi", "dvi", "vb", "lvb", "svb", "dvb", "vmin", "lvmin", "svmin", // + "dvmin", "vmax", "lvmax", "svmax", "dvmax", "cqw", "cqh", "cqi", "cqb", // + "cqmin", "cqmax", "cm", "mm", "q", "in", "pt", "pc", "px" + }); + + const std::set CompatUnitsAngle({ "deg", "grad", "rad", "turn" }); + const std::set CompatUnitsDPI({ "dpi", "dpcm", "dppx" }); + const std::set CompatUnitsFreq({ "hz", "khz" }); + const std::set CompatUnitsTime({ "s", "ms" }); + + const std::unordered_map> CompatsByUnit({ + { "em", CompatUnitsLen }, { "rem", CompatUnitsLen }, { "ex", CompatUnitsLen }, { "rex", CompatUnitsLen }, { "cap", CompatUnitsLen }, + { "rcap", CompatUnitsLen }, { "ch", CompatUnitsLen }, { "rch", CompatUnitsLen }, { "ic", CompatUnitsLen }, { "ric", CompatUnitsLen }, + { "lh", CompatUnitsLen }, { "rlh", CompatUnitsLen }, { "vw", CompatUnitsLen }, { "lvw", CompatUnitsLen }, { "svw", CompatUnitsLen }, + { "dvw", CompatUnitsLen }, { "vh", CompatUnitsLen }, { "lvh", CompatUnitsLen }, { "svh", CompatUnitsLen }, { "dvh", CompatUnitsLen }, + { "vi", CompatUnitsLen }, { "lvi", CompatUnitsLen }, { "svi", CompatUnitsLen }, { "dvi", CompatUnitsLen }, { "vb", CompatUnitsLen }, + { "lvb", CompatUnitsLen }, { "svb", CompatUnitsLen }, { "dvb", CompatUnitsLen }, { "vmin", CompatUnitsLen }, { "lvmin", CompatUnitsLen }, + { "svmin", CompatUnitsLen }, { "dvmin", CompatUnitsLen }, { "vmax", CompatUnitsLen }, { "lvmax", CompatUnitsLen }, { "svmax", CompatUnitsLen }, + { "dvmax", CompatUnitsLen }, { "cqw", CompatUnitsLen }, { "cqh", CompatUnitsLen }, { "cqi", CompatUnitsLen }, { "cqb", CompatUnitsLen }, + { "cqmin", CompatUnitsLen }, { "cqmax", CompatUnitsLen }, { "cm", CompatUnitsLen }, { "mm", CompatUnitsLen }, { "q", CompatUnitsLen }, + { "in", CompatUnitsLen }, { "pt", CompatUnitsLen }, { "pc", CompatUnitsLen }, { "px", CompatUnitsLen }, + { "deg", CompatUnitsAngle }, { "grad", CompatUnitsAngle }, { "rad", CompatUnitsAngle }, { "turn", CompatUnitsAngle }, + { "dpi", CompatUnitsDPI }, { "dpcm", CompatUnitsDPI }, { "dppx", CompatUnitsDPI }, + { "hz", CompatUnitsFreq }, { "khz", CompatUnitsFreq }, + { "s", CompatUnitsTime }, { "ms", CompatUnitsTime }, + }); + + static bool isaCustomUnit(const sass::string& unit) { + sass::string norm(StringUtils::toLowerCase(unit)); + if (KnownUnits.count(norm)) return false; + return string_to_unit(unit) == UnitType::UNKNOWN; + } - // this does not cover all cases (multiple preferred units) - double Units::convert_factor(const Units& r) const + // Returns true if unit is "unknown" + // Meaning we don't know to convert it + bool Units::isCustomUnit() const + { + for (auto n : numerators) if (isaCustomUnit(n)) return true; + for (auto d : denominators) if (isaCustomUnit(d)) return true; + return false; + } + // EO isCustomUnit + + bool Units::canCompareTo(const Units& r, bool legacy) const + { + if (legacy) return isComparableTo(r); + else return hasCompatibleUnits(r); + } + + bool Units::isComparableTo(const Units& r) const + { + if (numerators.size() != r.numerators.size()) return false; + if (denominators.size() != r.denominators.size()) return false; + return getUnitConversionFactor(r, false) != 0; + } + + bool Units::hasCompatibleUnits(const Units& r, bool strict) const + { + return getUnitConversionFactor(r, strict) != 0; + } + + // vh and px are possibly compatible, although we can't reduce on compile time + bool Units::hasPossiblyCompatibleUnits(const Units& r, bool strict) const + { + // If equal they are compatible + if (r == this) return true; + // Otherwise check our compatibility sets + if (r.numerators.size() == 1 && r.denominators.empty()) { + if (numerators.size() == 1 && denominators.empty()) { + sass::string lu(StringUtils::toLowerCase(numerators[0])); + sass::string ru(StringUtils::toLowerCase(r.numerators[0])); + // Most simple case where both have exactly one unit + auto kv = CompatsByUnit.find(lu); + // Unit has no known conversion possible + if (kv == CompatsByUnit.end()) return true; + // Check if second unit is fully compatible + if (kv->second.count(ru)) return true; + return CompatsByUnit.count(ru) == 0; + + } + } + if (r.numerators.empty() && r.denominators.empty()) { + return false; + } + if (numerators.empty() && denominators.size() == 0) { + return false; + } + std::cerr << "case not implemented\n"; + return false; + } + + // Return factor to convert into passed units + double Units::getUnitConversionFactor(const Units& r, bool strict) const { sass::vector miss_nums(0); @@ -409,8 +626,14 @@ namespace Sass { auto l_num_it = numerators.begin(); auto l_num_end = numerators.end(); - bool l_unitless = is_unitless(); - auto r_unitless = r.is_unitless(); + bool l_unitless = isUnitless(); + auto r_unitless = r.isUnitless(); + + if (strict) + { + if (!l_unitless && r_unitless) return 0.0; + if (l_unitless && !r_unitless) return 0.0; + } // overall conversion double factor = 1; @@ -421,6 +644,9 @@ namespace Sass { // get and increment afterwards const sass::string l_num = *(l_num_it ++); + // ToDo: we erase from base vector in the loop. + // Iterators might get invalid during the loop + // ToDo: refactor to use index access instead. auto r_num_it = r_nums.begin(), r_num_end = r_nums.end(); bool found = false; @@ -446,7 +672,7 @@ namespace Sass { } // maybe we did not find any // left numerator is leftover - if (!found) miss_nums.push_back(l_num); + if (!found) miss_nums.emplace_back(l_num); } auto l_den_it = denominators.begin(); @@ -484,24 +710,28 @@ namespace Sass { } // maybe we did not find any // left denominator is leftover - if (!found) miss_dens.push_back(l_den); + if (!found) miss_dens.emplace_back(l_den); } // check left-overs (ToDo: might cancel out?) if (miss_nums.size() > 0 && !r_unitless) { - throw Exception::IncompatibleUnits(r, *this); + return 0.0; } else if (miss_dens.size() > 0 && !r_unitless) { - throw Exception::IncompatibleUnits(r, *this); + return 0.0; } else if (r_nums.size() > 0 && !l_unitless) { - throw Exception::IncompatibleUnits(r, *this); + return 0.0; } else if (r_dens.size() > 0 && !l_unitless) { - throw Exception::IncompatibleUnits(r, *this); + return 0.0; } return factor; } + // EO getUnitConversionFactor + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// } diff --git a/src/units.hpp b/src/units.hpp index 98848849cc..271efc52e2 100644 --- a/src/units.hpp +++ b/src/units.hpp @@ -1,109 +1,220 @@ -#ifndef SASS_UNITS_H -#define SASS_UNITS_H +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#ifndef SASS_UNITS_HPP +#define SASS_UNITS_HPP -#include -#include -#include -#include +// sass.hpp must go before all system headers +// to get the __EXTENSIONS__ fix on Solaris. +#include "capi_sass.hpp" namespace Sass { - const double PI = std::acos(-1); + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// enum UnitClass { LENGTH = 0x000, - ANGLE = 0x100, - TIME = 0x200, + TIME = 0x100, + ANGLE = 0x200, FREQUENCY = 0x300, RESOLUTION = 0x400, INCOMMENSURABLE = 0x500 }; + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + enum UnitType { // size units - IN = UnitClass::LENGTH, + INCH = UnitClass::LENGTH, CM, PC, MM, PT, PX, + QMM, + + // time units + SEC = UnitClass::TIME, + MSEC, // angle units - DEG = ANGLE, + DEG = UnitClass::ANGLE, GRAD, RAD, TURN, - // time units - SEC = TIME, - MSEC, - // frequency units - HERTZ = FREQUENCY, + HERTZ = UnitClass::FREQUENCY, KHERTZ, // resolutions units - DPI = RESOLUTION, + DPI = UnitClass::RESOLUTION, DPCM, DPPX, // for unknown units - UNKNOWN = INCOMMENSURABLE + UNKNOWN = UnitClass::INCOMMENSURABLE }; + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + class Units { + + private: + + // Cache the final unit string + mutable sass::string stringified; + public: + + // The units in the numerator sass::vector numerators; + // The units in the denominator sass::vector denominators; - public: - // default constructor + + // Default constructor Units() : numerators(), denominators() { } + + // Construct from string + Units(const sass::string& u) : + numerators(), + denominators() + { + unit(u); + } + // copy constructor Units(const Units* ptr) : numerators(ptr->numerators), denominators(ptr->denominators) { } - // convert to string - sass::string unit() const; - // get if units are empty - bool is_unitless() const; - // return if valid for css - bool is_valid_css_unit() const; - // reduce units for output - // returns conversion factor + + // copy constructor + Units(const Units& ptr) : + numerators(ptr.numerators), + denominators(ptr.denominators) + { } + + // move constructor + Units(Units&& other) noexcept : + numerators(std::move(other.numerators)), + denominators(std::move(other.denominators)) + { } + + // Convert units to string + const sass::string& unit() const; + + // Reset unit without conversion factor + void unit(const sass::string& unit); + + // Returns true if empty + bool isUnitless() const; + + // Returns true if not empty + bool hasUnits() const { + return !isUnitless(); + } + + const sass::string& unit2() const; + + // Returns true if we only have given numerator + bool isOnlyOfUnit(sass::string numerator) const; + + // Returns true if valid for css + bool isValidCssUnit() const; + + // Cancel out all compatible unit classes + // E.g. `1000ms/s` will be reduced to `1` + // Returns factor to be applied to scalar double reduce(); - // normalize units for compare - // returns conversion factor + + // Normalize all units to the standard unit class + // Additionally sorts all units in ascending order + // In combination with `reduce` this allows numbers + // to be compared for equality independent of units + // E.g. '1000ms' will be normalized to '1s' + // Returns factor to be applied to scalar double normalize(); - // 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; + + // Compare units (without any normalizing) + bool operator==(const Units& rhs) const; + + // Delete other operators to make implementation more clear + // Helps us spot cases where we use undefined implementations + // bool operator!=(const Units& rhs) const = delete; + // bool operator>=(const Units& rhs) const = delete; + // bool operator<=(const Units& rhs) const = delete; + // bool operator>(const Units& rhs) const = delete; + // bool operator<(const Units& rhs) const = delete; + + // Returns true if unit is "unknown" + // Meaning we don't know to convert it + bool isCustomUnit() const; + + bool canCompareTo(const Units&, bool) const; + + bool isComparableTo(const Units&) const; + + // Return if conversion between units is possible + bool hasCompatibleUnits(const Units&, bool strict = false) const; + + // Returns whether [this] has units that are possibly + // compatible with [rhs], as defined by the Sass spec. + bool hasPossiblyCompatibleUnits(const Units&, bool strict = false) const; + + // Return factor to convert into passed units + double getUnitConversionFactor(const Units&, bool strict = false) const; + }; - extern const double size_conversion_factors[6][6]; + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + /* Declare matrix tables for unit conversion factors*/ + extern const double size_conversion_factors[7][7]; extern const double angle_conversion_factors[4][4]; extern const double time_conversion_factors[2][2]; extern const double frequency_conversion_factors[2][2]; extern const double resolution_conversion_factors[3][3]; - UnitType get_main_unit(const UnitClass unit); - enum Sass::UnitType string_to_unit(const sass::string&); - const char* unit_to_string(Sass::UnitType unit); - enum Sass::UnitClass get_unit_type(Sass::UnitType unit); - sass::string get_unit_class(Sass::UnitType unit); - sass::string unit_to_class(const sass::string&); - // throws incompatibleUnits exceptions - double conversion_factor(const sass::string&, const sass::string&); - double conversion_factor(UnitType, UnitType, UnitClass, UnitClass); - double convert_units(const sass::string&, const sass::string&, int&, int&); + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// + + // Return unit class enum for given unit type enum + UnitClass get_unit_class(UnitType unit); + + // Return standard unit for the given unit class enum + UnitType get_standard_unit(const UnitClass unit); + + // Return unit type enum from unit string + UnitType string_to_unit(const sass::string& s); + + // Return unit as string from unit type enum + const char* unit_to_string(UnitType unit); + + // Return conversion factor from s1 to s2 (returns zero for incompatible units) + double conversion_factor(const sass::string& s1, const sass::string& s2); + + // Return conversion factor from u1 to u2 (returns zero for incompatible units) + // Note: unit classes are passed as parameters since we mostly already have them + // Note: not sure how much performance this saves, but it fits our use-cases well + double conversion_factor(UnitType u1, UnitType u2, UnitClass c1, UnitClass c2); + + // Reduce units so that the result either is fully represented by lhs or rhs unit. + // Exponents are adjusted accordingly and returning factor must be applied to the scalar. + // Basically tries to cancel out compatible units (e.g. s/ms) and converts the remaining ones. + double reduce_units(const sass::string& lhs, const sass::string& rhs, int& lhsexp, int& rhsexp); + + ///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////// } diff --git a/src/utf8.h b/src/utf8.h deleted file mode 100644 index 82b13f59f9..0000000000 --- a/src/utf8.h +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright 2006 Nemanja Trifunovic - -/* -Permission is hereby granted, free of charge, to any person or organization -obtaining a copy of the software and accompanying documentation covered by -this license (the "Software") to use, reproduce, display, distribute, -execute, and transmit the Software, and to prepare derivative works of the -Software, and to permit third-parties to whom the Software is furnished to -do so, all subject to the following: - -The copyright notices in the Software and this entire statement, including -the above license grant, this restriction and the following disclaimer, -must be included in all copies of the Software, in whole or in part, and -all derivative works of the Software, unless such copies or derivative -works are solely in the form of machine-executable object code generated by -a source language processor. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT -SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE -FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, -ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -DEALINGS IN THE SOFTWARE. -*/ - - -#ifndef UTF8_FOR_CPP_2675DCD0_9480_4c0c_B92A_CC14C027B731 -#define UTF8_FOR_CPP_2675DCD0_9480_4c0c_B92A_CC14C027B731 - -#include "utf8/checked.h" -#include "utf8/unchecked.h" - -#endif // header guard diff --git a/src/utf8/checked.h b/src/utf8/checked.h index ce9d27c8ab..5749acaf54 100644 --- a/src/utf8/checked.h +++ b/src/utf8/checked.h @@ -50,7 +50,8 @@ namespace utf8 uint8_t u8; public: invalid_utf8 (uint8_t u) : u8(u) {} - virtual const char* what() const throw() { return "Invalid UTF-8"; } + virtual const char* what() const throw() { + return "Invalid UTF-8"; } uint8_t utf8_octet() const {return u8;} }; diff --git a/src/utf8_string.cpp b/src/utf8_string.cpp deleted file mode 100644 index 3464d93214..0000000000 --- a/src/utf8_string.cpp +++ /dev/null @@ -1,104 +0,0 @@ -// sass.hpp must go before all system headers to get the -// __EXTENSIONS__ fix on Solaris. -#include "sass.hpp" - -#include -#include -#include -#include - -#include "utf8.h" - -namespace Sass { - namespace UTF_8 { - - // naming conventions: - // offset: raw byte offset (0 based) - // position: code point offset (0 based) - // index: code point offset (1 based or negative) - - // function that will count the number of code points (utf-8 characters) from the given beginning to the given end - size_t code_point_count(const sass::string& str, size_t start, size_t end) { - return utf8::distance(str.begin() + start, str.begin() + end); - } - - size_t code_point_count(const sass::string& str) { - return utf8::distance(str.begin(), str.end()); - } - - // function that will return the byte offset at a code point position - size_t offset_at_position(const sass::string& str, size_t position) { - sass::string::const_iterator it = str.begin(); - utf8::advance(it, position, str.end()); - return std::distance(str.begin(), it); - } - - // function that returns number of bytes in a character at offset - size_t code_point_size_at_offset(const sass::string& str, size_t offset) { - // get iterator from string and forward by offset - sass::string::const_iterator stop = str.begin() + offset; - // check if beyond boundary - if (stop == str.end()) return 0; - // advance by one code point - utf8::advance(stop, 1, str.end()); - // calculate offset for code point - return stop - str.begin() - offset; - } - - // function that will return a normalized index, given a crazy one - size_t normalize_index(int index, size_t len) { - long signed_len = static_cast(len); - // assuming the index is 1-based - // we are returning a 0-based index - if (index > 0 && index <= signed_len) { - // positive and within string length - return index-1; - } - else if (index > signed_len) { - // positive and past string length - return len; - } - else if (index == 0) { - return 0; - } - else if (std::abs((double)index) <= signed_len) { - // negative and within string length - return index + signed_len; - } - else { - // negative and past string length - return 0; - } - } - - #ifdef _WIN32 - - // utf16 functions - using std::wstring; - - // convert from utf16/wide string to utf8 string - sass::string convert_from_utf16(const wstring& utf16) - { - sass::string utf8; - // pre-allocate expected memory - utf8.reserve(sizeof(utf16)/2); - utf8::utf16to8(utf16.begin(), utf16.end(), - back_inserter(utf8)); - return utf8; - } - - // convert from utf8 string to utf16/wide string - wstring convert_to_utf16(const sass::string& utf8) - { - wstring utf16; - // pre-allocate expected memory - utf16.reserve(code_point_count(utf8)*2); - utf8::utf8to16(utf8.begin(), utf8.end(), - back_inserter(utf16)); - return utf16; - } - - #endif - - } -} diff --git a/src/utf8_string.hpp b/src/utf8_string.hpp deleted file mode 100644 index 88b10f9b0b..0000000000 --- a/src/utf8_string.hpp +++ /dev/null @@ -1,38 +0,0 @@ -#ifndef SASS_UTF8_STRING_H -#define SASS_UTF8_STRING_H - -#include -#include "utf8.h" -#include "memory.hpp" - -namespace Sass { - namespace UTF_8 { - - // naming conventions: - // offset: raw byte offset (0 based) - // position: code point offset (0 based) - // index: code point offset (1 based or negative) - - // function that will count the number of code points (utf-8 characters) from the beginning to the given end - size_t code_point_count(const sass::string& str, size_t start, size_t end); - size_t code_point_count(const sass::string& str); - - // function that will return the byte offset of a code point in a - size_t offset_at_position(const sass::string& str, size_t position); - - // function that returns number of bytes in a character in a string - size_t code_point_size_at_offset(const sass::string& str, size_t offset); - - // function that will return a normalized index, given a crazy one - size_t normalize_index(int index, size_t len); - - #ifdef _WIN32 - // functions to handle unicode paths on windows - sass::string convert_from_utf16(const std::wstring& wstr); - std::wstring convert_to_utf16(const sass::string& str); - #endif - - } -} - -#endif diff --git a/src/util.cpp b/src/util.cpp deleted file mode 100644 index a675875f46..0000000000 --- a/src/util.cpp +++ /dev/null @@ -1,723 +0,0 @@ -#include "sass.hpp" -#include "sass.h" -#include "ast.hpp" -#include "util.hpp" -#include "util_string.hpp" -#include "lexer.hpp" -#include "prelexer.hpp" -#include "constants.hpp" -#include "utf8/checked.h" - -#include -#include -#if defined(_MSC_VER) && _MSC_VER >= 1800 && _MSC_VER < 1900 && defined(_M_X64) -#include -#endif - -namespace Sass { - - double round(double val, size_t precision) - { - // Disable FMA3-optimized implementation when compiling with VS2013 for x64 targets - // See https://github.com/sass/node-sass/issues/1854 for details - // FIXME: Remove this workaround when we switch to VS2015+ - #if defined(_MSC_VER) && _MSC_VER >= 1800 && _MSC_VER < 1900 && defined(_M_X64) - static std::once_flag flag; - std::call_once(flag, []() { _set_FMA3_enable(0); }); - #endif - - // https://github.com/sass/sass/commit/4e3e1d5684cc29073a507578fc977434ff488c93 - if (std::fmod(val, 1) - 0.5 > - std::pow(0.1, precision + 1)) return std::ceil(val); - else if (std::fmod(val, 1) - 0.5 > std::pow(0.1, precision)) return std::floor(val); - // work around some compiler issue - // cygwin has it not defined in std - using namespace std; - return ::round(val); - } - - /* Locale unspecific atof function. */ - double sass_strtod(const char *str) - { - char separator = *(localeconv()->decimal_point); - if(separator != '.'){ - // The current locale specifies another - // separator. convert the separator to the - // one understood by the locale if needed - const char *found = strchr(str, '.'); - if(found != NULL){ - // substitution is required. perform the substitution on a copy - // of the string. This is slower but it is thread safe. - char *copy = sass_copy_c_string(str); - *(copy + (found - str)) = separator; - double res = strtod(copy, NULL); - free(copy); - return res; - } - } - - return strtod(str, NULL); - } - - // helper for safe access to c_ctx - const char* safe_str (const char* str, const char* alt) { - return str == NULL ? alt : str; - } - - void free_string_array(char ** arr) { - if(!arr) - return; - - char **it = arr; - while (it && (*it)) { - free(*it); - ++it; - } - - free(arr); - } - - char **copy_strings(const sass::vector& strings, char*** array, int skip) { - int num = static_cast(strings.size()) - skip; - char** arr = (char**) calloc(num + 1, sizeof(char*)); - if (arr == 0) - return *array = (char **)NULL; - - for(int i = 0; i < num; i++) { - arr[i] = (char*) malloc(sizeof(char) * (strings[i + skip].size() + 1)); - if (arr[i] == 0) { - free_string_array(arr); - return *array = (char **)NULL; - } - std::copy(strings[i + skip].begin(), strings[i + skip].end(), arr[i]); - arr[i][strings[i + skip].size()] = '\0'; - } - - arr[num] = 0; - return *array = arr; - } - - // read css string (handle multiline DELIM) - sass::string read_css_string(const sass::string& str, bool css) - { - if (!css) return str; - sass::string out(""); - bool esc = false; - for (auto i : str) { - if (i == '\\') { - esc = ! esc; - } else if (esc && i == '\r') { - continue; - } else if (esc && i == '\n') { - out.resize (out.size () - 1); - esc = false; - continue; - } else { - esc = false; - } - out.push_back(i); - } - // happens when parsing does not correctly skip - // over escaped sequences for ie. interpolations - // one example: foo\#{interpolate} - // if (esc) out += '\\'; - return out; - } - - // double escape all escape sequences - // keep unescaped quotes and backslashes - sass::string evacuate_escapes(const sass::string& str) - { - sass::string out(""); - bool esc = false; - for (auto i : str) { - if (i == '\\' && !esc) { - out += '\\'; - out += '\\'; - esc = true; - } else if (esc && i == '"') { - out += '\\'; - out += i; - esc = false; - } else if (esc && i == '\'') { - out += '\\'; - out += i; - esc = false; - } else if (esc && i == '\\') { - out += '\\'; - out += i; - esc = false; - } else { - esc = false; - out += i; - } - } - // happens when parsing does not correctly skip - // over escaped sequences for ie. interpolations - // one example: foo\#{interpolate} - // if (esc) out += '\\'; - return out; - } - - // bell characters are replaced with spaces - void newline_to_space(sass::string& str) - { - std::replace(str.begin(), str.end(), '\n', ' '); - } - - // 1. Removes whitespace after newlines. - // 2. Replaces newlines with spaces. - // - // This method only considers LF and CRLF as newlines. - sass::string string_to_output(const sass::string& str) - { - sass::string result; - result.reserve(str.size()); - std::size_t pos = 0; - while (true) { - const std::size_t newline = str.find_first_of("\n\r", pos); - if (newline == sass::string::npos) break; - result.append(str, pos, newline - pos); - if (str[newline] == '\r') { - if (str[newline + 1] == '\n') { - pos = newline + 2; - } else { - // CR without LF: append as-is and continue. - result += '\r'; - pos = newline + 1; - continue; - } - } else { - pos = newline + 1; - } - result += ' '; - const std::size_t non_space = str.find_first_not_of(" \f\n\r\t\v", pos); - if (non_space != sass::string::npos) { - pos = non_space; - } - } - result.append(str, pos, sass::string::npos); - return result; - } - - sass::string escape_string(const sass::string& str) - { - sass::string out; - out.reserve(str.size()); - for (char c : str) { - switch (c) { - case '\n': - out.append("\\n"); - break; - case '\r': - out.append("\\r"); - break; - case '\f': - out.append("\\f"); - break; - default: - out += c; - } - } - return out; - } - - sass::string comment_to_compact_string(const sass::string& text) - { - sass::string str = ""; - size_t has = 0; - char prev = 0; - bool clean = false; - for (auto i : text) { - if (clean) { - if (i == '\n') { has = 0; } - else if (i == '\t') { ++ has; } - else if (i == ' ') { ++ has; } - else if (i == '*') {} - else { - clean = false; - str += ' '; - if (prev == '*' && i == '/') str += "*/"; - else str += i; - } - } else if (i == '\n') { - clean = true; - } else { - str += i; - } - prev = i; - } - if (has) return str; - else return text; - } - - // find best quote_mark by detecting if the string contains any single - // or double quotes. When a single quote is found, we not we want a double - // quote as quote_mark. Otherwise we check if the string cotains any double - // quotes, which will trigger the use of single quotes as best quote_mark. - char detect_best_quotemark(const char* s, char qm) - { - // ensure valid fallback quote_mark - char quote_mark = qm && qm != '*' ? qm : '"'; - while (*s) { - // force double quotes as soon - // as one single quote is found - if (*s == '\'') { return '"'; } - // a single does not force quote_mark - // maybe we see a double quote later - else if (*s == '"') { quote_mark = '\''; } - ++ s; - } - return quote_mark; - } - - sass::string read_hex_escapes(const sass::string& s) - { - - sass::string result; - bool skipped = false; - - for (size_t i = 0, L = s.length(); i < L; ++i) { - - // implement the same strange ruby sass behavior - // an escape sequence can also mean a unicode char - if (s[i] == '\\' && !skipped) { - - // remember - skipped = true; - - // escape length - size_t len = 1; - - // parse as many sequence chars as possible - // ToDo: Check if ruby aborts after possible max - while (i + len < L && s[i + len] && Util::ascii_isxdigit(static_cast(s[i + len]))) ++ len; - - if (len > 1) { - - // convert the extracted hex string to code point value - // ToDo: Maybe we could do this without creating a substring - uint32_t cp = strtol(s.substr (i + 1, len - 1).c_str(), NULL, 16); - - if (s[i + len] == ' ') ++ len; - - // assert invalid code points - if (cp == 0) cp = 0xFFFD; - // replace bell character - // if (cp == '\n') cp = 32; - - // use a very simple approach to convert via utf8 lib - // maybe there is a more elegant way; maybe we should - // convert the whole output from string to a stream!? - // allocate memory for utf8 char and convert to utf8 - unsigned char u[5] = {0,0,0,0,0}; utf8::append(cp, u); - for(size_t m = 0; m < 5 && u[m]; m++) result.push_back(u[m]); - - // skip some more chars? - i += len - 1; skipped = false; - - } - - else { - - skipped = false; - - result.push_back(s[i]); - - } - - } - - else { - - result.push_back(s[i]); - - } - - } - - return result; - - } - - sass::string unquote(const sass::string& s, char* qd, bool keep_utf8_sequences, bool strict) - { - - // not enough room for quotes - // no possibility to unquote - if (s.length() < 2) return s; - - char q; - bool skipped = false; - - // this is no guarantee that the unquoting will work - // what about whitespace before/after the quote_mark? - if (*s.begin() == '"' && *s.rbegin() == '"') q = '"'; - else if (*s.begin() == '\'' && *s.rbegin() == '\'') q = '\''; - else return s; - - sass::string unq; - unq.reserve(s.length()-2); - - for (size_t i = 1, L = s.length() - 1; i < L; ++i) { - - // implement the same strange ruby sass behavior - // an escape sequence can also mean a unicode char - if (s[i] == '\\' && !skipped) { - // remember - skipped = true; - - // skip it - // ++ i; - - // if (i == L) break; - - // escape length - size_t len = 1; - - // parse as many sequence chars as possible - // ToDo: Check if ruby aborts after possible max - while (i + len < L && s[i + len] && Util::ascii_isxdigit(static_cast(s[i + len]))) ++ len; - - // hex string? - if (keep_utf8_sequences) { - unq.push_back(s[i]); - } else if (len > 1) { - - // convert the extracted hex string to code point value - // ToDo: Maybe we could do this without creating a substring - uint32_t cp = strtol(s.substr (i + 1, len - 1).c_str(), NULL, 16); - - if (s[i + len] == ' ') ++ len; - - // assert invalid code points - if (cp == 0) cp = 0xFFFD; - // replace bell character - // if (cp == '\n') cp = 32; - - // use a very simple approach to convert via utf8 lib - // maybe there is a more elegant way; maybe we should - // convert the whole output from string to a stream!? - // allocate memory for utf8 char and convert to utf8 - unsigned char u[5] = {0,0,0,0,0}; utf8::append(cp, u); - for(size_t m = 0; m < 5 && u[m]; m++) unq.push_back(u[m]); - - // skip some more chars? - i += len - 1; skipped = false; - - } - - - } - // check for unexpected delimiter - // be strict and throw error back - // else if (!skipped && q == s[i]) { - // // don't be that strict - // return s; - // // this basically always means an internal error and not users fault - // error("Unescaped delimiter in string to unquote found. [" + s + "]", SourceSpan("[UNQUOTE]")); - // } - else { - if (strict && !skipped) { - if (s[i] == q) return s; - } - skipped = false; - unq.push_back(s[i]); - } - - } - if (skipped) { return s; } - if (qd) *qd = q; - return unq; - - } - - sass::string quote(const sass::string& s, char q) - { - - // autodetect with fallback to given quote - q = detect_best_quotemark(s.c_str(), q); - - // return an empty quoted string - if (s.empty()) return sass::string(2, q ? q : '"'); - - sass::string quoted; - quoted.reserve(s.length()+2); - quoted.push_back(q); - - const char* it = s.c_str(); - const char* end = it + strlen(it) + 1; - while (*it && it < end) { - const char* now = it; - - if (*it == q) { - quoted.push_back('\\'); - } else if (*it == '\\') { - quoted.push_back('\\'); - } - - int cp = utf8::next(it, end); - - // in case of \r, check if the next in sequence - // is \n and then advance the iterator and skip \r - if (cp == '\r' && it < end && utf8::peek_next(it, end) == '\n') { - cp = utf8::next(it, end); - } - - if (cp == '\n') { - quoted.push_back('\\'); - quoted.push_back('a'); - // we hope we can remove this flag once we figure out - // why ruby sass has these different output behaviors - // gsub(/\n(?![a-fA-F0-9\s])/, "\\a").gsub("\n", "\\a ") - using namespace Prelexer; - if (alternatives < - Prelexer::char_range<'a', 'f'>, - Prelexer::char_range<'A', 'F'>, - Prelexer::char_range<'0', '9'>, - space - >(it) != NULL) { - quoted.push_back(' '); - } - } else if (cp < 127) { - quoted.push_back((char) cp); - } else { - while (now < it) { - quoted.push_back(*now); - ++ now; - } - } - } - - quoted.push_back(q); - return quoted; - } - - bool is_hex_doublet(double n) - { - return n == 0x00 || n == 0x11 || n == 0x22 || n == 0x33 || - n == 0x44 || n == 0x55 || n == 0x66 || n == 0x77 || - n == 0x88 || n == 0x99 || n == 0xAA || n == 0xBB || - n == 0xCC || n == 0xDD || n == 0xEE || n == 0xFF ; - } - - bool is_color_doublet(double r, double g, double b) - { - return is_hex_doublet(r) && is_hex_doublet(g) && is_hex_doublet(b); - } - - bool peek_linefeed(const char* start) - { - using namespace Prelexer; - using namespace Constants; - return sequence < - zero_plus < - alternatives < - exactly <' '>, - exactly <'\t'>, - line_comment, - block_comment, - delimited_by < - slash_star, - star_slash, - false - > - > - >, - re_linebreak - >(start) != 0; - } - - namespace Util { - - bool isPrintable(StyleRule* r, Sass_Output_Style style) { - if (r == NULL) { - return false; - } - - Block_Obj b = r->block(); - - SelectorList* sl = r->selector(); - bool hasSelectors = sl ? sl->length() > 0 : false; - - if (!hasSelectors) { - return false; - } - - bool hasDeclarations = false; - bool hasPrintableChildBlocks = false; - for (size_t i = 0, L = b->length(); i < L; ++i) { - Statement_Obj stm = b->at(i); - if (Cast(stm)) { - return true; - } else if (Declaration* d = Cast(stm)) { - return isPrintable(d, style); - } else if (ParentStatement* p = Cast(stm)) { - Block_Obj pChildBlock = p->block(); - if (isPrintable(pChildBlock, style)) { - hasPrintableChildBlocks = true; - } - } else if (Comment* c = Cast(stm)) { - // keep for uncompressed - if (style != COMPRESSED) { - hasDeclarations = true; - } - // output style compressed - if (c->is_important()) { - hasDeclarations = c->is_important(); - } - } else { - hasDeclarations = true; - } - - if (hasDeclarations || hasPrintableChildBlocks) { - return true; - } - } - - return false; - } - - bool isPrintable(String_Constant* s, Sass_Output_Style style) - { - return ! s->value().empty(); - } - - bool isPrintable(String_Quoted* s, Sass_Output_Style style) - { - return true; - } - - bool isPrintable(Declaration* d, Sass_Output_Style style) - { - ExpressionObj val = d->value(); - if (String_Quoted_Obj sq = Cast(val)) return isPrintable(sq.ptr(), style); - if (String_Constant_Obj sc = Cast(val)) return isPrintable(sc.ptr(), style); - return true; - } - - bool isPrintable(SupportsRule* f, Sass_Output_Style style) { - if (f == NULL) { - return false; - } - - Block_Obj b = f->block(); - - bool hasDeclarations = false; - bool hasPrintableChildBlocks = false; - for (size_t i = 0, L = b->length(); i < L; ++i) { - Statement_Obj stm = b->at(i); - if (Cast(stm) || Cast(stm)) { - hasDeclarations = true; - } - else if (ParentStatement* b = Cast(stm)) { - Block_Obj pChildBlock = b->block(); - if (!b->is_invisible()) { - if (isPrintable(pChildBlock, style)) { - hasPrintableChildBlocks = true; - } - } - } - - if (hasDeclarations || hasPrintableChildBlocks) { - return true; - } - } - - return false; - } - - bool isPrintable(CssMediaRule* m, Sass_Output_Style style) - { - if (m == nullptr) return false; - Block_Obj b = m->block(); - if (b == nullptr) return false; - if (m->empty()) return false; - for (size_t i = 0, L = b->length(); i < L; ++i) { - Statement_Obj stm = b->at(i); - if (Cast(stm)) return true; - else if (Cast(stm)) return true; - else if (Comment* c = Cast(stm)) { - if (isPrintable(c, style)) { - return true; - } - } - else if (StyleRule* r = Cast(stm)) { - if (isPrintable(r, style)) { - return true; - } - } - else if (SupportsRule* f = Cast(stm)) { - if (isPrintable(f, style)) { - return true; - } - } - else if (CssMediaRule* mb = Cast(stm)) { - if (isPrintable(mb, style)) { - return true; - } - } - else if (ParentStatement* b = Cast(stm)) { - if (isPrintable(b->block(), style)) { - return true; - } - } - } - return false; - } - - bool isPrintable(Comment* c, Sass_Output_Style style) - { - // keep for uncompressed - if (style != COMPRESSED) { - return true; - } - // output style compressed - if (c->is_important()) { - return true; - } - // not printable - return false; - }; - - bool isPrintable(Block_Obj b, Sass_Output_Style style) { - if (!b) { - return false; - } - - for (size_t i = 0, L = b->length(); i < L; ++i) { - Statement_Obj stm = b->at(i); - if (Cast(stm) || Cast(stm)) { - return true; - } - else if (Comment* c = Cast(stm)) { - if (isPrintable(c, style)) { - return true; - } - } - else if (StyleRule* r = Cast(stm)) { - if (isPrintable(r, style)) { - return true; - } - } - else if (SupportsRule* f = Cast(stm)) { - if (isPrintable(f, style)) { - return true; - } - } - else if (CssMediaRule * m = Cast(stm)) { - if (isPrintable(m, style)) { - return true; - } - } - else if (ParentStatement* b = Cast(stm)) { - if (isPrintable(b->block(), style)) { - return true; - } - } - } - - return false; - } - - } -} diff --git a/src/util.hpp b/src/util.hpp deleted file mode 100644 index 04b0e78b2f..0000000000 --- a/src/util.hpp +++ /dev/null @@ -1,105 +0,0 @@ -#ifndef SASS_UTIL_H -#define SASS_UTIL_H - -// sass.hpp must go before all system headers to get the -// __EXTENSIONS__ fix on Solaris. -#include "sass.hpp" - -#include "sass/base.h" -#include "ast_fwd_decl.hpp" - -#include -#include -#include -#include -#include - -#define SASS_ASSERT(cond, msg) assert(cond && msg) - -namespace Sass { - - template - T clip(const T& n, const T& lower, const T& upper) { - return std::max(lower, std::min(n, upper)); - } - - template - T absmod(const T& n, const T& r) { - T m = std::fmod(n, r); - if (m < 0.0) m += r; - return m; - } - - double round(double val, size_t precision = 0); - double sass_strtod(const char* str); - const char* safe_str(const char *, const char* = ""); - void free_string_array(char **); - char **copy_strings(const sass::vector&, char ***, int = 0); - sass::string read_css_string(const sass::string& str, bool css = true); - sass::string evacuate_escapes(const sass::string& str); - sass::string string_to_output(const sass::string& str); - sass::string comment_to_compact_string(const sass::string& text); - sass::string read_hex_escapes(const sass::string& str); - sass::string escape_string(const sass::string& str); - void newline_to_space(sass::string& str); - - sass::string quote(const sass::string&, char q = 0); - sass::string unquote(const sass::string&, char* q = 0, bool keep_utf8_sequences = false, bool strict = true); - char detect_best_quotemark(const char* s, char qm = '"'); - - bool is_hex_doublet(double n); - bool is_color_doublet(double r, double g, double b); - - bool peek_linefeed(const char* start); - - // Returns true iff `elements` ⊆ `container`. - template - bool contains_all(C container, T elements) { - for (const auto &el : elements) { - if (container.find(el) == container.end()) return false; - } - return true; - } - - // C++20 `starts_with` equivalent. - // See https://en.cppreference.com/w/cpp/string/basic_string/starts_with - inline bool starts_with(const sass::string& str, const char* prefix, size_t prefix_len) { - return str.compare(0, prefix_len, prefix) == 0; - } - - inline bool starts_with(const sass::string& str, const char* prefix) { - return starts_with(str, prefix, std::strlen(prefix)); - } - - // C++20 `ends_with` equivalent. - // See https://en.cppreference.com/w/cpp/string/basic_string/ends_with - inline bool ends_with(const sass::string& str, const sass::string& suffix) { - return suffix.size() <= str.size() && std::equal(suffix.rbegin(), suffix.rend(), str.rbegin()); - } - - inline bool ends_with(const sass::string& str, const char* suffix, size_t suffix_len) { - if (suffix_len > str.size()) return false; - const char* suffix_it = suffix + suffix_len; - const char* str_it = str.c_str() + str.size(); - while (suffix_it != suffix) if (*(--suffix_it) != *(--str_it)) return false; - return true; - } - - inline bool ends_with(const sass::string& str, const char* suffix) { - return ends_with(str, suffix, std::strlen(suffix)); - } - - namespace Util { - - bool isPrintable(StyleRule* r, Sass_Output_Style style = NESTED); - bool isPrintable(SupportsRule* r, Sass_Output_Style style = NESTED); - bool isPrintable(CssMediaRule* r, Sass_Output_Style style = NESTED); - bool isPrintable(Comment* b, Sass_Output_Style style = NESTED); - bool isPrintable(Block_Obj b, Sass_Output_Style style = NESTED); - bool isPrintable(String_Constant* s, Sass_Output_Style style = NESTED); - bool isPrintable(String_Quoted* s, Sass_Output_Style style = NESTED); - bool isPrintable(Declaration* d, Sass_Output_Style style = NESTED); - - } -} -#endif diff --git a/src/util_string.cpp b/src/util_string.cpp deleted file mode 100644 index d16c4cfcc4..0000000000 --- a/src/util_string.cpp +++ /dev/null @@ -1,125 +0,0 @@ -#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 sass::string& test) { - // Work directly on characters - const char* src = test.c_str(); - // There is a small chance that the search string - // Is longer than the rest of the string to look at - while (*lit && (*src == *lit || *src + 32 == *lit)) { - ++src, ++lit; - } - // True if literal is at end - // If not test was too long - return *lit == 0; - } - - void ascii_str_tolower(sass::string* s) { - for (auto& ch : *s) { - ch = ascii_tolower(static_cast(ch)); - } - } - - void ascii_str_toupper(sass::string* s) { - for (auto& ch : *s) { - ch = ascii_toupper(static_cast(ch)); - } - } - - sass::string rtrim(sass::string str) { - auto it = std::find_if_not(str.rbegin(), str.rend(), ascii_isspace); - str.erase(str.rend() - it); - return str; - } - - // ########################################################################### - // Returns [name] without a vendor prefix. - // If [name] has no vendor prefix, it's returned as-is. - // ########################################################################### - sass::string unvendor(const sass::string& name) - { - if (name.size() < 2) return name; - 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 - - sass::string normalize_newlines(const sass::string& str) { - sass::string result; - result.reserve(str.size()); - std::size_t pos = 0; - while (true) { - const std::size_t newline = str.find_first_of("\n\f\r", pos); - if (newline == sass::string::npos) break; - result.append(str, pos, newline - pos); - result += '\n'; - if (str[newline] == '\r' && str[newline + 1] == '\n') { - pos = newline + 2; - } - else { - pos = newline + 1; - } - } - result.append(str, pos, sass::string::npos); - return result; - } - - sass::string normalize_underscores(const sass::string& str) { - sass::string normalized = str; - std::replace(normalized.begin(), normalized.end(), '_', '-'); - return normalized; - } - - sass::string normalize_decimals(const sass::string& str) { - sass::string normalized; - if (!str.empty() && str[0] == '.') { - normalized.reserve(str.size() + 1); - normalized += '0'; - normalized += str; - } - else { - normalized = str; - } - return normalized; - } - - char opening_bracket_for(char closing_bracket) { - switch (closing_bracket) { - case ')': return '('; - case ']': return '['; - case '}': return '{'; - default: return '\0'; - } - } - - char closing_bracket_for(char opening_bracket) { - switch (opening_bracket) { - case '(': return ')'; - case '[': return ']'; - case '{': return '}'; - default: return '\0'; - } - } - - } - // namespace Util - -} -// namespace Sass diff --git a/src/util_string.hpp b/src/util_string.hpp deleted file mode 100644 index 837230d362..0000000000 --- a/src/util_string.hpp +++ /dev/null @@ -1,73 +0,0 @@ -#ifndef SASS_UTIL_STRING_H -#define SASS_UTIL_STRING_H - -#include "sass.hpp" -#include - -namespace Sass { - namespace Util { - - // ########################################################################## - // Special case insensitive string matcher. We can optimize - // the more general compare case quite a bit by requiring - // consumers to obey some rules (lowercase and no space). - // - `literal` must only contain lower case ascii characters - // there is one edge case where this could give false positives - // test could contain a (non-ascii) char exactly 32 below literal - // ########################################################################## - bool equalsLiteral(const char* lit, const sass::string& test); - - // ########################################################################### - // Returns [name] without a vendor prefix. - // If [name] has no vendor prefix, it's returned as-is. - // ########################################################################### - sass::string unvendor(const sass::string& name); - - sass::string rtrim(sass::string str); - sass::string normalize_newlines(const sass::string& str); - sass::string normalize_underscores(const sass::string& str); - sass::string normalize_decimals(const sass::string& str); - char opening_bracket_for(char closing_bracket); - char closing_bracket_for(char opening_bracket); - - // Locale-independent ASCII character routines. - - inline bool ascii_isalpha(unsigned char c) { - return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z'); - } - - inline bool ascii_isdigit(unsigned char c) { - return (c >= '0' && c <= '9'); - } - - inline bool ascii_isalnum(unsigned char c) { - return ascii_isalpha(c) || ascii_isdigit(c); - } - - inline bool ascii_isascii(unsigned char c) { return c < 128; } - - inline bool ascii_isxdigit(unsigned char c) { - return ascii_isdigit(c) || (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f'); - } - - inline bool ascii_isspace(unsigned char c) { - return c == ' ' || c == '\t' || c == '\v' || c == '\f' || c == '\r' || c == '\n'; - } - - inline char ascii_tolower(unsigned char c) { - if (c >= 'A' && c <= 'Z') return c + 32; - return c; - } - - void ascii_str_tolower(sass::string* s); - - inline char ascii_toupper(unsigned char c) { - if (c >= 'a' && c <= 'z') return c - 32; - return c; - } - - void ascii_str_toupper(sass::string* s); - - } // namespace Sass -} // namespace Util -#endif // SASS_UTIL_STRING_H diff --git a/src/values.cpp b/src/values.cpp deleted file mode 100644 index 092bfd89cf..0000000000 --- a/src/values.cpp +++ /dev/null @@ -1,140 +0,0 @@ -// sass.hpp must go before all system headers to get the -// __EXTENSIONS__ fix on Solaris. -#include "sass.hpp" - -#include "sass.h" -#include "values.hpp" - -#include - -namespace Sass { - - // convert value from C++ side to C-API - union Sass_Value* ast_node_to_sass_value (const Expression* val) - { - if (val->concrete_type() == Expression::NUMBER) - { - const Number* res = Cast(val); - return sass_make_number(res->value(), res->unit().c_str()); - } - else if (val->concrete_type() == Expression::COLOR) - { - if (const Color_RGBA* rgba = Cast(val)) { - return sass_make_color(rgba->r(), rgba->g(), rgba->b(), rgba->a()); - } else { - // ToDo: allow to also use HSLA colors!! - Color_RGBA_Obj col = Cast(val)->copyAsRGBA(); - return sass_make_color(col->r(), col->g(), col->b(), col->a()); - } - } - else if (val->concrete_type() == Expression::LIST) - { - const List* l = Cast(val); - union Sass_Value* list = sass_make_list(l->size(), l->separator(), l->is_bracketed()); - for (size_t i = 0, L = l->length(); i < L; ++i) { - ExpressionObj obj = l->at(i); - auto val = ast_node_to_sass_value(obj); - sass_list_set_value(list, i, val); - } - return list; - } - else if (val->concrete_type() == Expression::MAP) - { - const Map* m = Cast(val); - union Sass_Value* map = sass_make_map(m->length()); - size_t i = 0; for (ExpressionObj key : m->keys()) { - sass_map_set_key(map, i, ast_node_to_sass_value(key)); - sass_map_set_value(map, i, ast_node_to_sass_value(m->at(key))); - ++ i; - } - return map; - } - else if (val->concrete_type() == Expression::NULL_VAL) - { - return sass_make_null(); - } - else if (val->concrete_type() == Expression::BOOLEAN) - { - const Boolean* res = Cast(val); - return sass_make_boolean(res->value()); - } - else if (val->concrete_type() == Expression::STRING) - { - if (const String_Quoted* qstr = Cast(val)) - { - return sass_make_qstring(qstr->value().c_str()); - } - else if (const String_Constant* cstr = Cast(val)) - { - return sass_make_string(cstr->value().c_str()); - } - } - return sass_make_error("unknown sass value type"); - } - - // convert value from C-API to C++ side - Value* sass_value_to_ast_node (const union Sass_Value* val) - { - switch (sass_value_get_tag(val)) { - case SASS_NUMBER: - return SASS_MEMORY_NEW(Number, - SourceSpan("[C-VALUE]"), - sass_number_get_value(val), - sass_number_get_unit(val)); - case SASS_BOOLEAN: - return SASS_MEMORY_NEW(Boolean, - SourceSpan("[C-VALUE]"), - sass_boolean_get_value(val)); - case SASS_COLOR: - // ToDo: allow to also use HSLA colors!! - return SASS_MEMORY_NEW(Color_RGBA, - SourceSpan("[C-VALUE]"), - sass_color_get_r(val), - sass_color_get_g(val), - sass_color_get_b(val), - sass_color_get_a(val)); - case SASS_STRING: - if (sass_string_is_quoted(val)) { - return SASS_MEMORY_NEW(String_Quoted, - SourceSpan("[C-VALUE]"), - sass_string_get_value(val)); - } - return SASS_MEMORY_NEW(String_Constant, - SourceSpan("[C-VALUE]"), - sass_string_get_value(val)); - case SASS_LIST: { - List* l = SASS_MEMORY_NEW(List, - SourceSpan("[C-VALUE]"), - sass_list_get_length(val), - sass_list_get_separator(val)); - for (size_t i = 0, L = sass_list_get_length(val); i < L; ++i) { - l->append(sass_value_to_ast_node(sass_list_get_value(val, i))); - } - l->is_bracketed(sass_list_get_is_bracketed(val)); - return l; - } - case SASS_MAP: { - Map* m = SASS_MEMORY_NEW(Map, SourceSpan("[C-VALUE]")); - for (size_t i = 0, L = sass_map_get_length(val); i < L; ++i) { - *m << std::make_pair( - sass_value_to_ast_node(sass_map_get_key(val, i)), - sass_value_to_ast_node(sass_map_get_value(val, i))); - } - return m; - } - case SASS_NULL: - return SASS_MEMORY_NEW(Null, SourceSpan("[C-VALUE]")); - case SASS_ERROR: - return SASS_MEMORY_NEW(Custom_Error, - SourceSpan("[C-VALUE]"), - sass_error_get_message(val)); - case SASS_WARNING: - return SASS_MEMORY_NEW(Custom_Warning, - SourceSpan("[C-VALUE]"), - sass_warning_get_message(val)); - default: break; - } - return 0; - } - -} diff --git a/src/values.hpp b/src/values.hpp deleted file mode 100644 index 3c4c687b0e..0000000000 --- a/src/values.hpp +++ /dev/null @@ -1,12 +0,0 @@ -#ifndef SASS_VALUES_H -#define SASS_VALUES_H - -#include "ast.hpp" - -namespace Sass { - - union Sass_Value* ast_node_to_sass_value (const Expression* val); - Value* sass_value_to_ast_node (const union Sass_Value* val); - -} -#endif diff --git a/src/visitor_css.hpp b/src/visitor_css.hpp new file mode 100644 index 0000000000..211a3bff30 --- /dev/null +++ b/src/visitor_css.hpp @@ -0,0 +1,47 @@ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#ifndef SASS_VISITOR_CSS_HPP +#define SASS_VISITOR_CSS_HPP + +// sass.hpp must go before all system headers +// to get the __EXTENSIONS__ fix on Solaris. +#include "capi_sass.hpp" + +#include "ast_fwd_decl.hpp" + +namespace Sass { + + // An interface for [visitors][] that traverse CSS statements. + // [visitors]: https://en.wikipedia.org/wiki/Visitor_pattern + + template + class CssVisitor { + public: + + virtual T visitCssAtRule(CssAtRule* css) = 0; + virtual T visitCssComment(CssComment* css) = 0; + virtual T visitCssDeclaration(CssDeclaration* css) = 0; + virtual T visitCssImport(CssImport* css) = 0; + virtual T visitCssKeyframeBlock(CssKeyframeBlock* css) = 0; + virtual T visitCssMediaRule(CssMediaRule* css) = 0; + virtual T visitCssRoot(CssRoot* css) = 0; // LibSass only + virtual T visitCssStyleRule(CssStyleRule* css) = 0; + virtual T visitCssSupportsRule(CssSupportsRule* css) = 0; + + }; + + template + class CssVisitable { + public: + virtual T accept(CssVisitor* visitor) = 0; + }; + +} + +#define DECLARE_CSS_ACCEPT(T, name)\ + T accept(CssVisitor* visitor) override final {\ + return visitor->visit##name(this);\ + }\ + +#endif diff --git a/src/visitor_expression.hpp b/src/visitor_expression.hpp new file mode 100644 index 0000000000..fd5b1330c1 --- /dev/null +++ b/src/visitor_expression.hpp @@ -0,0 +1,50 @@ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#ifndef SASS_VISITOR_EXPRESSION_HPP +#define SASS_VISITOR_EXPRESSION_HPP + +// sass.hpp must go before all system headers +// to get the __EXTENSIONS__ fix on Solaris. +#include "capi_sass.hpp" + +#include "ast_fwd_decl.hpp" + +namespace Sass { + + // An interface for [visitors][] that traverse SassScript expressions. + // [visitors]: https://en.wikipedia.org/wiki/Visitor_pattern + + template + class ExpressionVisitor { + public: + + virtual T visitBinaryOpExpression(BinaryOpExpression*) = 0; + virtual T visitBooleanExpression(BooleanExpression*) = 0; + virtual T visitColorExpression(ColorExpression*) = 0; + virtual T visitItplFnExpression(ItplFnExpression*) = 0; + virtual T visitFunctionExpression(FunctionExpression*) = 0; + virtual T visitIfExpression(IfExpression*) = 0; + virtual T visitListExpression(ListExpression*) = 0; + virtual T visitMapExpression(MapExpression*) = 0; + virtual T visitNullExpression(NullExpression*) = 0; + virtual T visitNumberExpression(NumberExpression*) = 0; + virtual T visitParenthesizedExpression(ParenthesizedExpression*) = 0; + virtual T visitSelectorExpression(SelectorExpression*) = 0; + virtual T visitStringExpression(StringExpression*) = 0; + virtual T visitSupportsExpression(SupportsExpression*) = 0; + virtual T visitUnaryOpExpression(UnaryOpExpression*) = 0; + virtual T visitValueExpression(ValueExpression*) = 0; + virtual T visitVariableExpression(VariableExpression*) = 0; + + }; + + template + class ExpressionVisitable { + public: + virtual T accept(ExpressionVisitor* visitor) = 0; + }; + +} + +#endif diff --git a/src/visitor_is_calc_safe.cpp b/src/visitor_is_calc_safe.cpp new file mode 100644 index 0000000000..d7e9b83e3d --- /dev/null +++ b/src/visitor_is_calc_safe.cpp @@ -0,0 +1,58 @@ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#include "visitor_is_calc_safe.hpp" + +#include "ast_expressions.hpp" + +#include + +namespace Sass { + + bool isMathOperator(SassOperator op) + { + if (op == MUL) return true; + if (op == DIV) return true; + if (op == ADD) return true; + if (op == SUB) return true; + return false; + } + + bool SelectorExpression::isCalcSafe() { + return false; + } + + bool BinaryOpExpression::isCalcSafe() + { + if (isMathOperator(operand())) { + return left()->isCalcSafe() + || right()->isCalcSafe(); + } + return false; + } + + bool ListExpression::isCalcSafe() + { + if (separator() != SASS_SPACE) return false; + if (hasBrackets() == true) return false; + if (size() < 2) return false; + for(auto asd : items()) { + if (!asd->isCalcSafe()) + return false; + } + return true; + } + + bool ParenthesizedExpression::isCalcSafe() + { + return expression()->isCalcSafe(); + } + + bool StringExpression::isCalcSafe() + { + // auto str = text()->getInitialPlain(); + // Requires a bit more testings + return true; + } + +} diff --git a/src/visitor_is_calc_safe.hpp b/src/visitor_is_calc_safe.hpp new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/visitor_selector.hpp b/src/visitor_selector.hpp new file mode 100644 index 0000000000..a9c7ba3439 --- /dev/null +++ b/src/visitor_selector.hpp @@ -0,0 +1,47 @@ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#ifndef SASS_VISITOR_SELECTOR_HPP +#define SASS_VISITOR_SELECTOR_HPP + +// sass.hpp must go before all system headers +// to get the __EXTENSIONS__ fix on Solaris. +#include "capi_sass.hpp" + +#include "ast_fwd_decl.hpp" + +namespace Sass { + + // An interface for [visitors][] that traverse selectors. + // [visitors]: https://en.wikipedia.org/wiki/Visitor_pattern + + template + // AnySelectorVisitor + class SelectorVisitor { + public: + + virtual T visitAttributeSelector(AttributeSelector* attribute) = 0; + virtual T visitClassSelector(ClassSelector* klass) = 0; + virtual T visitComplexSelector(ComplexSelector* complex) = 0; + virtual T visitCompoundSelector(CompoundSelector* compound) = 0; + virtual T visitIDSelector(IDSelector* id) = 0; + virtual T visitPlaceholderSelector(PlaceholderSelector* placeholder) = 0; + virtual T visitPseudoSelector(PseudoSelector* pseudo) = 0; + virtual T visitSelectorList(SelectorList* list) = 0; + virtual T visitTypeSelector(TypeSelector* type) = 0; + // The following two types have been optimized out in libsass + // virtual T visitParentSelector(ParentSelector* parent) = 0; + // virtual T visitUniversalSelector(UniversalSelector* universal) = 0; + // virtual T visitSelectorCombinator(SelectorCombinator* combinator) = 0; + + }; + + template + class SelectorVisitable { + public: + virtual T accept(SelectorVisitor* visitor) = 0; + }; + +} + +#endif diff --git a/src/visitor_statement.hpp b/src/visitor_statement.hpp new file mode 100644 index 0000000000..0328d97b27 --- /dev/null +++ b/src/visitor_statement.hpp @@ -0,0 +1,68 @@ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#ifndef SASS_VISITOR_STATEMENT_HPP +#define SASS_VISITOR_STATEMENT_HPP + +// sass.hpp must go before all system headers +// to get the __EXTENSIONS__ fix on Solaris. +#include "capi_sass.hpp" + +#include "ast_fwd_decl.hpp" + +namespace Sass { + + // An interface for [visitors][] that traverse Sass statements. + // [visitors]: https://en.wikipedia.org/wiki/Visitor_pattern + + template + class StatementVisitor { + public: + + bool inImport81 = false; + + virtual T visitAtRootRule(AtRootRule*) = 0; + virtual T visitAtRule(AtRule*) = 0; + virtual T visitContentBlock(ContentBlock*) = 0; + virtual T visitContentRule(ContentRule*) = 0; + virtual T visitDebugRule(DebugRule*) = 0; + virtual T visitDeclaration(Declaration*) = 0; + virtual T visitEachRule(EachRule*) = 0; + virtual T visitErrorRule(ErrorRule*) = 0; + virtual T visitExtendRule(ExtendRule*) = 0; + virtual T visitForRule(ForRule*) = 0; + virtual T visitForwardRule(ForwardRule*) = 0; + virtual T visitFunctionRule(FunctionRule*) = 0; + virtual T visitIfRule(IfRule*) = 0; + virtual T visitImportRule(ImportRule*) = 0; + virtual T visitIncludeRule(IncludeRule*) = 0; + virtual T visitLoudComment(LoudComment*) = 0; + virtual T visitMediaRule(MediaRule*) = 0; + virtual T visitMixinRule(MixinRule*) = 0; + virtual T visitReturnRule(ReturnRule*) = 0; + virtual T visitSilentComment(SilentComment*) = 0; + virtual T visitStyleRule(StyleRule*) = 0; + //virtual T visitStylesheet(Stylesheet*) = 0; + virtual T visitSupportsRule(SupportsRule*) = 0; + virtual T visitUseRule(UseRule*) = 0; + // Renamed from visitVariableDeclaration + virtual T visitAssignRule(AssignRule*) = 0; + virtual T visitWarnRule(WarnRule*) = 0; + virtual T visitWhileRule(WhileRule*) = 0; + + }; + + template + class StatementVisitable { + public: + virtual T accept(StatementVisitor* visitor) = 0; + }; + +} + +#define DECLARE_STATEMENT_ACCEPT(T, name)\ + T accept(StatementVisitor* visitor) override final {\ + return visitor->visit##name(this);\ + }\ + +#endif diff --git a/src/visitor_value.hpp b/src/visitor_value.hpp new file mode 100644 index 0000000000..26ad9a4b4d --- /dev/null +++ b/src/visitor_value.hpp @@ -0,0 +1,49 @@ +/*****************************************************************************/ +/* Part of LibSass, released under the MIT license (See LICENSE.txt). */ +/*****************************************************************************/ +#ifndef SASS_VISITOR_VALUE_HPP +#define SASS_VISITOR_VALUE_HPP + +// sass.hpp must go before all system headers +// to get the __EXTENSIONS__ fix on Solaris. +#include "capi_sass.hpp" + +#include "ast_fwd_decl.hpp" + +namespace Sass { + + // An interface for [visitors][] that traverse SassScript values. + // [visitors]: https://en.wikipedia.org/wiki/Visitor_pattern + + template + class ValueVisitor { + public: + + virtual T visitBoolean(Boolean* value) = 0; + virtual T visitColor(Color* value) = 0; + virtual T visitFunction(Function* value) = 0; + virtual T visitCalculation(Calculation* value) = 0; + virtual T visitCalcOperation(CalcOperation* value) = 0; + virtual T visitMixin(Mixin* value) = 0; + virtual T visitList(List* value) = 0; + virtual T visitMap(Map* value) = 0; + virtual T visitNull(Null* value) = 0; + virtual T visitNumber(Number* value) = 0; + virtual T visitString(String* value) = 0; + + }; + + template + class ValueVisitable { + public: + virtual T accept(ValueVisitor* visitor) = 0; + }; + +} + +#define DECLARE_VALUE_ACCEPT(T, name)\ + T accept(ValueVisitor* visitor) override final {\ + return visitor->visit##name(this);\ + }\ + +#endif diff --git a/test/Makefile b/test/Makefile index ac512f0ebb..065ced3a99 100644 --- a/test/Makefile +++ b/test/Makefile @@ -9,10 +9,28 @@ LDFLAGS += -std=$(LIBSASS_CPPSTD) test: test_shared_ptr test_util_string test_shared_ptr: build/test_shared_ptr - @ASAN_OPTIONS="symbolize=1" build/test_shared_ptr + build/test_shared_ptr -test_util_string: build/test_util_string - @ASAN_OPTIONS="symbolize=1" build/test_util_string +test_ordered_map: build/test_ordered_map + build/test_ordered_map + +test_character: build/test_character + build/test_character + +test_string_utils: build/test_string_utils + build/test_string_utils + +test_source_data: build/test_source_data + build/test_source_data + +test_lurlparser: build/test_lurlparser + build/test_lurlparser + +test_offset: build/test_offset + build/test_offset + +test_units: build/test_units + build/test_units build: @mkdir build @@ -20,10 +38,28 @@ build: build/test_shared_ptr: test_shared_ptr.cpp ../src/memory/shared_ptr.cpp | build $(CXX) $(CXXFLAGS) ../src/memory/allocator.cpp ../src/memory/shared_ptr.cpp -o build/test_shared_ptr test_shared_ptr.cpp -build/test_util_string: test_util_string.cpp ../src/util_string.cpp | build - $(CXX) $(CXXFLAGS) ../src/memory/allocator.cpp ../src/util_string.cpp -o build/test_util_string test_util_string.cpp +build/test_ordered_map: test_ordered_map.cpp ../src/memory/shared_ptr.cpp | build + $(CXX) $(CXXFLAGS) ../src/memory/allocator.cpp -o build/test_ordered_map test_ordered_map.cpp + +build/test_character: test_character.cpp ../src/character.cpp | build + $(CXX) $(CXXFLAGS) ../src/memory/allocator.cpp ../src/character.cpp -o build/test_character test_character.cpp + +build/test_string_utils: test_string_utils.cpp ../src/string_utils.cpp | build + $(CXX) $(CXXFLAGS) ../src/memory/allocator.cpp ../src/character.cpp ../src/string_utils.cpp -o build/test_string_utils test_string_utils.cpp + +build/test_source_data: test_source_data.cpp ../src/source.cpp ../src/offset.cpp ../src/position.cpp | build + $(CXX) $(CXXFLAGS) ../src/memory/allocator.cpp ../src/randomize.cpp -o build/test_source_data test_source_data.cpp ../src/source.cpp ../src/source_span.cpp ../src/source_state.cpp ../src/offset.cpp ../src/position.cpp + +build/test_lurlparser: test_lurlparser.cpp ../src/LUrlParser/LUrlParser.cpp | build + $(CXX) $(CXXFLAGS) -o build/test_lurlparser test_lurlparser.cpp ../src/LUrlParser/LUrlParser.cpp + +build/test_offset: test_offset.cpp ../src/offset.cpp | build + $(CXX) $(CXXFLAGS) ../src/memory/allocator.cpp -o build/test_offset test_offset.cpp ../src/offset.cpp + +build/test_units: test_units.cpp ../src/units.cpp | build + $(CXX) $(CXXFLAGS) ../src/memory/allocator.cpp -o build/test_units test_units.cpp ../src/units.cpp clean: | build rm -rf build -.PHONY: test test_shared_ptr test_util_string clean +.PHONY: test test_shared_ptr test_ordered_map test_string_utils test_source_data test_lurlparser test_offset clean diff --git a/test/assert.hpp b/test/assert.hpp new file mode 100644 index 0000000000..085beb85d3 --- /dev/null +++ b/test/assert.hpp @@ -0,0 +1,88 @@ +#ifndef ASSERT_HPP +#define ASSERT_HPP + +#include +#include +#include +#include "../src/memory/allocator.hpp" +#include "../src/offset.hpp" + +sass::string escape_string(const sass::string& str) { + sass::string out; + out.reserve(str.size()); + for (char c : str) { + switch (c) { + case '\n': + out.append("\\n"); + break; + case '\r': + out.append("\\r"); + break; + case '\f': + out.append("\\f"); + break; + default: + out += c; + } + } + return out; +} + +#define ASSERT(cond) \ + if (!(cond)) { \ + std::cerr << "Assertion failed: " #cond " at " __FILE__ << ":" << __LINE__ << std::endl; \ + return false; \ + } \ + +#define ASSERT_TRUE(cond) \ + if (!(cond)) { \ + std::cerr << \ + "Expected condition to be true at " << __FILE__ << ":" << __LINE__ << \ + std::endl; \ + return false; \ + } \ + +#define ASSERT_FALSE(cond) \ + ASSERT_TRUE(!(cond)) \ + +#define ASSERT_NR_EQ(a, b) \ + if (a != b) { \ + std::cerr << std::setprecision(12) << \ + "Expected LHS == RHS at " << __FILE__ << ":" << __LINE__ << \ + "\n LHS: [" << a << "]" \ + "\n RHS: [" << b << "]" << \ + std::endl; \ + return false; \ + } \ + +#define ASSERT_STR_EQ(a, b) \ + if (sass::string(b) != a) { \ + std::cerr << \ + "Expected LHS == RHS at " << __FILE__ << ":" << __LINE__ << \ + "\n LHS: [" << escape_string(a) << "]" \ + "\n RHS: [" << escape_string(b) << "]" << \ + std::endl; \ + return false; \ + } \ + +#define INIT_TEST_RESULTS \ + sass::vector passed; \ + sass::vector failed; \ + +#define TEST(fn) \ + if (fn()) { \ + passed.push_back(#fn); \ + } else { \ + failed.push_back(#fn); \ + std::cerr << "Failed: " #fn << std::endl; \ + } \ + +#define REPORT_TEST_RESULTS \ + std::cerr << argv[0] << ": Passed: " << passed.size() \ + << ", failed: " << failed.size() \ + << "." << std::endl; \ + return failed.size(); \ + + + +#endif diff --git a/test/test_lurlparser.cpp b/test/test_lurlparser.cpp new file mode 100644 index 0000000000..45e87d27a3 --- /dev/null +++ b/test/test_lurlparser.cpp @@ -0,0 +1,32 @@ +#include "../src/memory/shared_ptr.hpp" +#include "../src/LUrlParser/LUrlParser.hpp" +#include "assert.hpp" + +#include +#include +#include +#include + +bool TestLUrlParser() { + using LUrlParser::clParseURL; + clParseURL URL = clParseURL::ParseURL( + "https://John:Dow@github.com:443/corporateshark/LUrlParser?query#hash"); + if (URL.IsValid()) + { + ASSERT_STR_EQ(URL.m_Scheme, "https"); + ASSERT_STR_EQ(URL.m_Host, "github.com"); + ASSERT_STR_EQ(URL.m_Port, "443"); + ASSERT_STR_EQ(URL.m_Path, "corporateshark/LUrlParser"); + ASSERT_STR_EQ(URL.m_Query, "query"); + ASSERT_STR_EQ(URL.m_Fragment, "hash"); + ASSERT_STR_EQ(URL.m_UserName, "John"); + ASSERT_STR_EQ(URL.m_Password, "Dow"); + } + return true; +} + +int main(int argc, char **argv) { + INIT_TEST_RESULTS; + TEST(TestLUrlParser); + REPORT_TEST_RESULTS; +} diff --git a/test/test_offset.cpp b/test/test_offset.cpp new file mode 100644 index 0000000000..acaee1ff1e --- /dev/null +++ b/test/test_offset.cpp @@ -0,0 +1,33 @@ +#include "../src/position.hpp" +#include "assert.hpp" + +#include +#include +#include +#include + +using namespace Sass; + +bool testOffsetMove() { + const char* text1 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + ASSERT_STR_EQ(Offset::move(text1, Offset::init(0, 8)), + "IJKLMNOPQRSTUVWXYZ"); + const char* text2 = "ABC\nDEFGHIJKLMNOPQRSTUVW\nXYZ"; + ASSERT_STR_EQ(Offset::move(text2, Offset::init(1, 5)), + "IJKLMNOPQRSTUVW\nXYZ"); + ASSERT_STR_EQ(Offset::move(text2, Offset::init(1, 0)), + "DEFGHIJKLMNOPQRSTUVW\nXYZ"); + ASSERT_STR_EQ(Offset::move(text2, Offset::init(2, 0)), + "XYZ"); + ASSERT_STR_EQ(Offset::move(text2, Offset::init(2, 3)), ""); + ASSERT_NR_EQ(Offset::move(text2, Offset::init(3, 5)), 0); + ASSERT_NR_EQ(Offset::move(text2, Offset::init(2, 4)), 0); + ASSERT_NR_EQ(Offset::move(text2, Offset::init(1, 20)), 0); + return true; +} + +int main(int argc, char **argv) { + INIT_TEST_RESULTS; + TEST(testOffsetMove); + REPORT_TEST_RESULTS; +} diff --git a/test/test_ordered_map.cpp b/test/test_ordered_map.cpp new file mode 100644 index 0000000000..e2ca9f93ae --- /dev/null +++ b/test/test_ordered_map.cpp @@ -0,0 +1,76 @@ +#include "../src/ordered_map.hpp" +#include "assert.hpp" + +#include +#include +#include +#include + +bool TestOrderedMap() { + Sass::ordered_map map; + ASSERT_NR_EQ(map.size(), 0); + map.push_back("first", 42); + // Test size getter + ASSERT_NR_EQ(map.size(), 1); + // Test front/back accessor + ASSERT_NR_EQ(map.front().first, "first"); + ASSERT_NR_EQ(map.front().second, 42); + ASSERT_NR_EQ(map.back().first, "first"); + ASSERT_NR_EQ(map.back().second, 42); + // Test array accessor + map["first"] = 1; // change + map["second"] = 2; // append + map["third"] = 3; // append + // Test count getter + ASSERT_NR_EQ(map.count("first"), 1); + ASSERT_NR_EQ(map.count("seven"), 0); + // Test array accessors + ASSERT_NR_EQ(map[0].first, "first"); + ASSERT_NR_EQ(map[1].first, "second"); + ASSERT_NR_EQ(map[2].first, "third"); + ASSERT_NR_EQ(map["first"], 1); + ASSERT_NR_EQ(map["second"], 2); + ASSERT_NR_EQ(map["third"], 3); + // Test size getter + ASSERT_NR_EQ(map.size(), 3); + // Test front/back accessor + ASSERT_NR_EQ(map.front().first, "first"); + ASSERT_NR_EQ(map.front().second, 1); + ASSERT_NR_EQ(map.back().first, "third"); + ASSERT_NR_EQ(map.back().second, 3); + // Erase front item by key + ASSERT_TRUE(map.erase("first")); + // Test size getter + ASSERT_NR_EQ(map.size(), 2); + // Test front/back accessor + ASSERT_NR_EQ(map.front().first, "second"); + ASSERT_NR_EQ(map.front().second, 2); + // Erase front item by index + ASSERT_TRUE(map.erase(0)); + // Test size getter + ASSERT_NR_EQ(map.size(), 1); + // Test front/back accessor + ASSERT_NR_EQ(map.front().first, "third"); + ASSERT_NR_EQ(map.front().second, 3); + // Search iterator by key + auto it = map.find("third"); + // Test iterator accessor + ASSERT_NR_EQ(it->first, "third"); + ASSERT_NR_EQ(it->second, 3); + // Test iterator modifier + it->second = 42; + // Check again by fetching front + ASSERT_NR_EQ(map[0].second, 42); + ASSERT_NR_EQ(map.front().second, 42); + // Erase front item by index + ASSERT_TRUE(map.erase("third")); + ASSERT_TRUE(map.empty()); + // All have passed + return true; +} + +int main(int argc, char **argv) { + INIT_TEST_RESULTS; + TEST(TestOrderedMap); + REPORT_TEST_RESULTS; +} diff --git a/test/test_shared_ptr.cpp b/test/test_shared_ptr.cpp index 7355c1abd8..e317795ddd 100644 --- a/test/test_shared_ptr.cpp +++ b/test/test_shared_ptr.cpp @@ -1,23 +1,17 @@ -#include "../src/memory/allocator.hpp" #include "../src/memory/shared_ptr.hpp" +#include "assert.hpp" #include #include #include #include -#define ASSERT(cond) \ - if (!(cond)) { \ - std::cerr << "Assertion failed: " #cond " at " __FILE__ << ":" << __LINE__ << std::endl; \ - return false; \ - } \ - class TestObj : public Sass::SharedObj { public: TestObj(bool *destroyed) : destroyed_(destroyed) {} ~TestObj() { *destroyed_ = true; } Sass::sass::string to_string() const { - Sass::sass::ostream result; + Sass::sass::sstream result; result << "refcount=" << refcount << " destroyed=" << *destroyed_; return result.str(); } @@ -30,7 +24,7 @@ using SharedTestObj = Sass::SharedImpl; bool TestOneSharedPtr() { bool destroyed = false; { - SharedTestObj a = SASS_MEMORY_NEW(TestObj, &destroyed); + SharedTestObj a = new TestObj(&destroyed); } ASSERT(destroyed); return true; @@ -39,7 +33,7 @@ bool TestOneSharedPtr() { bool TestTwoSharedPtrs() { bool destroyed = false; { - SharedTestObj a = SASS_MEMORY_NEW(TestObj, &destroyed); + SharedTestObj a = new TestObj(&destroyed); { SharedTestObj b = a; } @@ -52,7 +46,7 @@ bool TestTwoSharedPtrs() { bool TestSelfAssignment() { bool destroyed = false; { - SharedTestObj a = SASS_MEMORY_NEW(TestObj, &destroyed); + SharedTestObj a = new TestObj(&destroyed); a = a; ASSERT(!destroyed); } @@ -162,17 +156,8 @@ bool TestComparisonWithNullptr() { return true; } -#define TEST(fn) \ - if (fn()) { \ - passed.push_back(#fn); \ - } else { \ - failed.push_back(#fn); \ - std::cerr << "Failed: " #fn << std::endl; \ - } \ - int main(int argc, char **argv) { - std::vector passed; - std::vector failed; + INIT_TEST_RESULTS; TEST(TestOneSharedPtr); TEST(TestTwoSharedPtrs); TEST(TestSelfAssignment); @@ -184,8 +169,5 @@ int main(int argc, char **argv) { TEST(TestDetachNull); TEST(TestComparisonWithSharedPtr); TEST(TestComparisonWithNullptr); - std::cerr << argv[0] << ": Passed: " << passed.size() - << ", failed: " << failed.size() - << "." << std::endl; - return failed.size(); + REPORT_TEST_RESULTS; } diff --git a/test/test_source_data.cpp b/test/test_source_data.cpp new file mode 100644 index 0000000000..c02b1517c4 --- /dev/null +++ b/test/test_source_data.cpp @@ -0,0 +1,235 @@ +#include "../src/source.hpp" +#include "assert.hpp" + +#include +#include +#include +#include + +namespace { + +using namespace Sass; + +bool testSourceFileBasic() { + const char* txt = + "Line A\n" + "Line B\n" + "Line C"; + SourceFile source("sass://", txt, -1); + ASSERT_NR_EQ(source.countLines(), 3); + ASSERT_STR_EQ(source.getLine(0), "Line A"); + ASSERT_STR_EQ(source.getLine(1), "Line B"); + ASSERT_STR_EQ(source.getLine(2), "Line C"); + return true; +} + +bool testSourceFileCrLf() { + const char* txt = + "Line A\r\n" + "Line B\r\n" + "Line C\r"; + SourceFile source("sass://", txt, -1); + ASSERT_NR_EQ(source.countLines(), 3); + ASSERT_STR_EQ(source.getLine(0), "Line A"); + ASSERT_STR_EQ(source.getLine(1), "Line B"); + ASSERT_STR_EQ(source.getLine(2), "Line C"); + return true; +} + +bool testSourceFileEmpty() { + const char* txt = + "\n" + "\n" + ""; + SourceFile source("sass://", txt, -1); + ASSERT_NR_EQ(source.countLines(), 3); + ASSERT_STR_EQ(source.getLine(0), ""); + ASSERT_STR_EQ(source.getLine(1), ""); + ASSERT_STR_EQ(source.getLine(2), ""); + return true; +} + +bool testSourceFileEmptyCrLf() { + const char* txt = + "\r\n" + "\r\n" + "\r"; + SourceFile source("sass://", txt, -1); + ASSERT_NR_EQ(source.countLines(), 3); + ASSERT_STR_EQ(source.getLine(0), ""); + ASSERT_STR_EQ(source.getLine(1), ""); + ASSERT_STR_EQ(source.getLine(2), ""); + return true; +} + +bool testSourceFileEmptyTrail() { + const char* txt = + "\n" + "\n" + "\n"; + SourceFile source("sass://", txt, -1); + ASSERT_NR_EQ(source.countLines(), 4); + ASSERT_STR_EQ(source.getLine(0), ""); + ASSERT_STR_EQ(source.getLine(1), ""); + ASSERT_STR_EQ(source.getLine(2), ""); + ASSERT_STR_EQ(source.getLine(3), ""); + return true; +} + +bool testSyntheticFileBasic() { + const char* around = + "Line A\n" + "Line B\n" + "Line C\n" + "Line D\n" + "Line E"; + SourceFileObj parent = SASS_MEMORY_NEW( + SourceFile, "sass://", around, -1); + SourceSpan pstate(parent, + Offset::init(1, 4), + Offset::init(0, 0)); + SyntheticFile source("[ADD]", parent, pstate); + ASSERT_NR_EQ(source.countLines(), 5); + ASSERT_STR_EQ(source.getLine(0), "Line A"); + ASSERT_STR_EQ(source.getLine(1), "Line[ADD] B"); + ASSERT_STR_EQ(source.getLine(2), "Line C"); + ASSERT_STR_EQ(source.getLine(3), "Line D"); + ASSERT_STR_EQ(source.getLine(4), "Line E"); + return true; +} + +bool testSyntheticFileBasicMulti() { + const char* around = + "Line A\n" + "Line B\n" + "Line C\n" + "Line D\n" + "Line E"; + SourceFileObj parent = SASS_MEMORY_NEW( + SourceFile, "sass://", around, -1); + SourceSpan pstate(parent, + Offset::init(1, 1), + Offset::init(1, 2)); + SyntheticFile source("[ADD]", parent, pstate); + ASSERT_NR_EQ(source.countLines(), 4); + ASSERT_STR_EQ(source.getLine(0), "Line A"); + ASSERT_STR_EQ(source.getLine(1), "L[ADD]ne C"); + ASSERT_STR_EQ(source.getLine(2), "Line D"); + ASSERT_STR_EQ(source.getLine(3), "Line E"); + return true; +} + +bool testSyntheticFileMulti() { + const char* around = + "Line A\n" + "Line B\n" + "Line C\n" + "Line D\n" + "Line E"; + SourceFileObj parent = SASS_MEMORY_NEW( + SourceFile, "sass://", around, -1); + SourceSpan pstate(parent, + Offset::init(1, 4), + Offset::init(0, 0)); + SyntheticFile source("[ADD]\n[ANOTHER]\n[MORE]", parent, pstate); + ASSERT_NR_EQ(source.countLines(), 7); + ASSERT_STR_EQ(source.getLine(0), "Line A"); + ASSERT_STR_EQ(source.getLine(1), "Line[ADD]"); + ASSERT_STR_EQ(source.getLine(2), "[ANOTHER]"); + ASSERT_STR_EQ(source.getLine(3), "[MORE] B"); + ASSERT_STR_EQ(source.getLine(4), "Line C"); + ASSERT_STR_EQ(source.getLine(5), "Line D"); + ASSERT_STR_EQ(source.getLine(6), "Line E"); + return true; +} + +bool testSyntheticFileMultiMulti() { + const char* around = + "Line A\n" + "Line B\n" + "Line C\n" + "Line D\n" + "Line E"; + SourceFileObj parent = SASS_MEMORY_NEW( + SourceFile, "sass://", around, -1); + SourceSpan pstate(parent, + Offset::init(1, 4), + Offset::init(1, 5)); + SyntheticFile source("[ADD]\n[ANOTHER]\n[MORE]", parent, pstate); + ASSERT_NR_EQ(source.countLines(), 6); + ASSERT_STR_EQ(source.getLine(0), "Line A"); + ASSERT_STR_EQ(source.getLine(1), "Line[ADD]"); + ASSERT_STR_EQ(source.getLine(2), "[ANOTHER]"); + ASSERT_STR_EQ(source.getLine(3), "[MORE]C"); + ASSERT_STR_EQ(source.getLine(4), "Line D"); + ASSERT_STR_EQ(source.getLine(5), "Line E"); + return true; +} + +bool testSourceFileUnicode() { + const char* txt = + "Line A\n" + "[a=b ï]\n" + "Line C"; + SourceFile source("sass://", txt, -1); + ASSERT_NR_EQ(source.countLines(), 3); + ASSERT_STR_EQ(source.getLine(0), "Line A"); + ASSERT_STR_EQ(source.getLine(1), "[a=b ï]"); + ASSERT_STR_EQ(source.getLine(2), "Line C"); + return true; +} + +bool testSyntheticFileUnicode1() { + const char* around = + "Line A\n" + "[ä=ö ï]\n" + "Line C"; + SourceFileObj parent = SASS_MEMORY_NEW( + SourceFile, "sass://", around, -1); + SourceSpan pstate(parent, + Offset::init(1, 1), + Offset::init(0, 4)); + SyntheticFile source("[ADD]", parent, pstate); + ASSERT_NR_EQ(source.countLines(), 3); + ASSERT_STR_EQ(source.getLine(0), "Line A"); + ASSERT_STR_EQ(source.getLine(1), "[[ADD]ï]"); + ASSERT_STR_EQ(source.getLine(2), "Line C"); + return true; +} + +bool testSyntheticFileUnicode2() { + const char* around = + "Line A\n" + "[ä=ö ï]\n" + "Line C"; + SourceFileObj parent = SASS_MEMORY_NEW( + SourceFile, "sass://", around, -1); + SourceSpan pstate(parent, + Offset::init(1, 2), + Offset::init(0, 3)); + SyntheticFile source("[ADD]", parent, pstate); + ASSERT_NR_EQ(source.countLines(), 3); + ASSERT_STR_EQ(source.getLine(0), "Line A"); + ASSERT_STR_EQ(source.getLine(1), "[ä[ADD]ï]"); + ASSERT_STR_EQ(source.getLine(2), "Line C"); + return true; +} + +} // namespace + +int main(int argc, char **argv) { + INIT_TEST_RESULTS; + TEST(testSourceFileBasic); + TEST(testSourceFileCrLf); + TEST(testSourceFileEmpty); + TEST(testSourceFileEmptyCrLf); + TEST(testSourceFileEmptyTrail); + TEST(testSourceFileUnicode); + TEST(testSyntheticFileBasic); + TEST(testSyntheticFileMulti); + TEST(testSyntheticFileBasicMulti); + TEST(testSyntheticFileMultiMulti); + TEST(testSyntheticFileUnicode1); + TEST(testSyntheticFileUnicode2); + REPORT_TEST_RESULTS; +} diff --git a/test/test_util_string.cpp b/test/test_util_string.cpp index a44e6e1670..a180afcee0 100644 --- a/test/test_util_string.cpp +++ b/test/test_util_string.cpp @@ -1,4 +1,5 @@ #include "../src/util_string.hpp" +#include "assert.hpp" #include #include @@ -7,104 +8,6 @@ namespace { -Sass::sass::string escape_string(const Sass::sass::string& str) { - Sass::sass::string out; - out.reserve(str.size()); - for (char c : str) { - switch (c) { - case '\n': - out.append("\\n"); - break; - case '\r': - out.append("\\r"); - break; - case '\f': - out.append("\\f"); - break; - default: - out += c; - } - } - return out; -} - -#define ASSERT_TRUE(cond) \ - if (!cond) { \ - std::cerr << \ - "Expected condition to be true at " << __FILE__ << ":" << __LINE__ << \ - std::endl; \ - return false; \ - } \ - -#define ASSERT_FALSE(cond) \ - ASSERT_TRUE(!(cond)) \ - -#define ASSERT_STR_EQ(a, b) \ - if (a != b) { \ - std::cerr << \ - "Expected LHS == RHS at " << __FILE__ << ":" << __LINE__ << \ - "\n LHS: [" << escape_string(a) << "]" \ - "\n RHS: [" << escape_string(b) << "]" << \ - std::endl; \ - return false; \ - } \ - -bool TestNormalizeNewlinesNoNewline() { - Sass::sass::string input = "a"; - Sass::sass::string normalized = Sass::Util::normalize_newlines(input); - ASSERT_STR_EQ(input, normalized); - return true; -} - -bool TestNormalizeNewlinesLF() { - Sass::sass::string input = "a\nb"; - Sass::sass::string normalized = Sass::Util::normalize_newlines(input); - ASSERT_STR_EQ(input, normalized); - return true; -} - -bool TestNormalizeNewlinesCR() { - Sass::sass::string normalized = Sass::Util::normalize_newlines("a\rb"); - ASSERT_STR_EQ("a\nb", normalized); - return true; -} - -bool TestNormalizeNewlinesCRLF() { - Sass::sass::string normalized = Sass::Util::normalize_newlines("a\r\nb\r\n"); - ASSERT_STR_EQ("a\nb\n", normalized); - return true; -} - -bool TestNormalizeNewlinesFF() { - Sass::sass::string normalized = Sass::Util::normalize_newlines("a\fb\f"); - ASSERT_STR_EQ("a\nb\n", normalized); - return true; -} - -bool TestNormalizeNewlinesMixed() { - Sass::sass::string normalized = Sass::Util::normalize_newlines("a\fb\nc\rd\r\ne\ff"); - ASSERT_STR_EQ("a\nb\nc\nd\ne\nf", normalized); - return true; -} - -bool TestNormalizeUnderscores() { - Sass::sass::string normalized = Sass::Util::normalize_underscores("a_b_c"); - ASSERT_STR_EQ("a-b-c", normalized); - return true; -} - -bool TestNormalizeDecimalsLeadingZero() { - Sass::sass::string normalized = Sass::Util::normalize_decimals("0.5"); - ASSERT_STR_EQ("0.5", normalized); - return true; -} - -bool TestNormalizeDecimalsNoLeadingZero() { - Sass::sass::string normalized = Sass::Util::normalize_decimals(".5"); - ASSERT_STR_EQ("0.5", normalized); - return true; -} - bool testEqualsLiteral() { ASSERT_TRUE(Sass::Util::equalsLiteral("moz", "moz")); ASSERT_TRUE(Sass::Util::equalsLiteral(":moz", ":moz")); @@ -142,6 +45,33 @@ bool TestUnvendor() { return true; } +bool TestSplitString1() { + Sass::sass::vector list = + Sass::Util::split_string("a,b,c", ','); + ASSERT_NR_EQ(3, list.size()); + ASSERT_STR_EQ("a", list[0]); + ASSERT_STR_EQ("b", list[1]); + ASSERT_STR_EQ("c", list[2]); + return true; +} + +bool TestSplitString2() { + Sass::sass::vector list = + Sass::Util::split_string("a,b,", ','); + ASSERT_NR_EQ(3, list.size()); + ASSERT_STR_EQ("a", list[0]); + ASSERT_STR_EQ("b", list[1]); + ASSERT_STR_EQ("", list[2]); + return true; +} + +bool TestSplitStringEmpty() { + Sass::sass::vector list = + Sass::Util::split_string("", ','); + ASSERT_NR_EQ(0, list.size()); + return true; +} + bool Test_ascii_str_to_lower() { Sass::sass::string str = "A B"; Sass::Util::ascii_str_tolower(&str); @@ -151,6 +81,7 @@ bool Test_ascii_str_to_lower() { bool Test_ascii_str_to_upper() { Sass::sass::string str = "a b"; + ASSERT_STR_EQ("A B", Sass::Util::ascii_str_toupper(str)); Sass::Util::ascii_str_toupper(&str); ASSERT_STR_EQ("A B", str); return true; @@ -181,37 +112,35 @@ bool Test_ascii_isspace() { return true; } -} // namespace +bool TestEqualsIgnoreSeparator() { + ASSERT_TRUE(Sass::Util::ascii_str_equals_ignore_separator("fOo", "FoO")); + ASSERT_TRUE(Sass::Util::ascii_str_equals_ignore_separator("fOo-BaR", "FoO_bAr")); + ASSERT_TRUE(Sass::Util::ascii_str_equals_ignore_separator("FoO_bAr", "fOo-BaR")); + return true; +} -#define TEST(fn) \ - if (fn()) { \ - passed.push_back(#fn); \ - } else { \ - failed.push_back(#fn); \ - std::cerr << "Failed: " #fn << std::endl; \ - } \ +bool TestHashIgnoreSeparator() { + ASSERT_TRUE(Sass::Util::hash_ignore_separator("fOo") == Sass::Util::hash_ignore_separator("FoO")); + ASSERT_TRUE(Sass::Util::hash_ignore_separator("fOo-BaR") == Sass::Util::hash_ignore_separator("FoO_bAr")); + ASSERT_TRUE(Sass::Util::hash_ignore_separator("FoO_bAr") == Sass::Util::hash_ignore_separator("fOo-BaR")); + return true; +} + +} // namespace int main(int argc, char **argv) { - std::vector passed; - std::vector failed; - TEST(TestNormalizeNewlinesNoNewline); - TEST(TestNormalizeNewlinesLF); - TEST(TestNormalizeNewlinesCR); - TEST(TestNormalizeNewlinesCRLF); - TEST(TestNormalizeNewlinesFF); - TEST(TestNormalizeNewlinesMixed); - TEST(TestNormalizeUnderscores); - TEST(TestNormalizeDecimalsLeadingZero); - TEST(TestNormalizeDecimalsNoLeadingZero); + INIT_TEST_RESULTS; TEST(testEqualsLiteral); TEST(TestUnvendor); + TEST(TestSplitStringEmpty); + TEST(TestSplitString1); + TEST(TestSplitString2); TEST(Test_ascii_str_to_lower); TEST(Test_ascii_str_to_upper); TEST(Test_ascii_isalpha); TEST(Test_ascii_isxdigit); TEST(Test_ascii_isspace); - std::cerr << argv[0] << ": Passed: " << passed.size() - << ", failed: " << failed.size() - << "." << std::endl; - return failed.size(); + TEST(TestEqualsIgnoreSeparator); + // TEST(TestHashIgnoreSeparator); + REPORT_TEST_RESULTS; } diff --git a/utils/build-skeletons/libsass.targets b/utils/build-skeletons/libsass.targets index 77bcf669b4..957970e14d 100644 --- a/utils/build-skeletons/libsass.targets +++ b/utils/build-skeletons/libsass.targets @@ -1,8 +1,13 @@ - {{includes}} + {{api_includes}} + {{api_headers}} + {{api_sources}} + {{ast_headers}} + {{ast_sources}} + {{parser_headers}} + {{parser_sources}} + {{fn_headers}} + {{fn_sources}} {{headers}} {{sources}} - - - diff --git a/utils/build-skeletons/libsass.vcxproj.filters b/utils/build-skeletons/libsass.vcxproj.filters index 78cace5ddc..0adff5b44f 100644 --- a/utils/build-skeletons/libsass.vcxproj.filters +++ b/utils/build-skeletons/libsass.vcxproj.filters @@ -5,16 +5,60 @@ {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms - - {93995380-89BD-4b04-88EB-625FBE52EBFB} + + {93995380-89BD-4b04-88EB-625FBE52EB92} + h;hh;hpp;hxx;hm;in;inl;inc;xsd + + + {93995380-89BD-4b04-88EB-625FBE52EB93} h;hh;hpp;hxx;hm;in;inl;inc;xsd - + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + {93995380-89BD-4b04-88EB-625FBE52EB94} + h;hh;hpp;hxx;hm;in;inl;inc;xsd + + + {4FC737F1-C7A5-4376-A066-2A32D752A2F4} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EB97} + h;hh;hpp;hxx;hm;in;inl;inc;xsd + + + {4FC737F1-C7A5-4376-A066-2A32D752A274} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89B2-4b04-88EB-625FBE52EB97} + h;hh;hpp;hxx;hm;in;inl;inc;xsd + + + {4FC737F1-C735-4376-A066-2A32D752A274} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;hm;in;inl;inc;xsd + + + {4FC737F1-C7A5-4376-A066-2A32D752A2F3} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + - {{includes}} - {{headers}} - {{sources}} + {{api_includes}} + {{api_headers}} + {{api_sources}} + {{ast_headers}} + {{ast_sources}} + {{parser_headers}} + {{parser_sources}} + {{fn_headers}} + {{fn_sources}} + {{headers}} + {{sources}} diff --git a/utils/update-builds.pl b/utils/update-builds.pl index 72cb412095..f11bd2c1d1 100644 --- a/utils/update-builds.pl +++ b/utils/update-builds.pl @@ -7,26 +7,74 @@ # Alternative `cpanm install File::Slurp` use File::Slurp qw(read_file write_file); -my $tmpl_msvc_head = < -EOTMPL - my $tmpl_msvc_inc = < EOTMPL +my $tmpl_msvc_head = < +EOTMPL + my $tmpl_msvc_src = < EOTMPL -my $tmpl_msvc_filter_head = < - Library Includes + API Includes EOTMPL -my $tmpl_msvc_filter_inc = < +my $tmpl_msvc_filter_api_head = < + CAPI Headers + +EOTMPL + +my $tmpl_msvc_filter_api_src = < + CAPI Sources + +EOTMPL + +my $tmpl_msvc_filter_ast_head = < + AST Headers + +EOTMPL + +my $tmpl_msvc_filter_ast_src = < + AST Sources + +EOTMPL + +my $tmpl_msvc_filter_parser_head = < + Parser Headers + +EOTMPL + +my $tmpl_msvc_filter_parser_src = < + Parser Sources + +EOTMPL + +my $tmpl_msvc_filter_fn_head = < + Function Headers + +EOTMPL + +my $tmpl_msvc_filter_fn_src = < + Function Sources + +EOTMPL + +my $tmpl_msvc_filter_head = < LibSass Headers EOTMPL @@ -72,17 +120,53 @@ ($@) @SOURCES = map { s/\//\\/gr } @SOURCES; @CSOURCES = map { s/\//\\/gr } @CSOURCES; +my @APIHEADERS = grep { m/^capi[_.]/} @HPPFILES; +my @LIBHEADERS = grep { not m/^capi[_.]/} @HPPFILES; +my @APISOURCES = grep { m/^capi[_.]/} @SOURCES, @CSOURCES; +my @LIBSOURCES = grep { not m/^capi[_.]/} @SOURCES, @CSOURCES; + +my @ASTHEADERS = grep { m/^ast[_.]/} @LIBHEADERS; +@LIBHEADERS = grep { not m/^ast[_.]/} @LIBHEADERS; +my @ASTSOURCES = grep { m/^ast[_.]/} @LIBSOURCES; +@LIBSOURCES = grep { not m/^ast[_.]/} @LIBSOURCES; + +my @PARSERHEADERS = grep { m/^parser[_.]/} @LIBHEADERS; +@LIBHEADERS = grep { not m/^parser[_.]/} @LIBHEADERS; +my @PARSERSOURCES = grep { m/^parser[_.]/} @LIBSOURCES; +@LIBSOURCES = grep { not m/^parser[_.]/} @LIBSOURCES; + +my @FNHEADERS = grep { m/^fn[_.]/} @LIBHEADERS; +@LIBHEADERS = grep { not m/^fn[_.]/} @LIBHEADERS; +my @FNSOURCES = grep { m/^fn[_.]/} @LIBSOURCES; +@LIBSOURCES = grep { not m/^fn[_.]/} @LIBSOURCES; + my $targets = read_file("build-skeletons/libsass.targets"); -$targets =~s /\{\{includes\}\}/renderTemplate($tmpl_msvc_inc, @INCFILES)/eg; -$targets =~s /\{\{headers\}\}/renderTemplate($tmpl_msvc_head, @HPPFILES)/eg; -$targets =~s /\{\{sources\}\}/renderTemplate($tmpl_msvc_src, @SOURCES, @CSOURCES)/eg; +$targets =~s /\{\{api_includes\}\}/renderTemplate($tmpl_msvc_inc, @INCFILES)/eg; +$targets =~s /\{\{api_headers\}\}/renderTemplate($tmpl_msvc_head, @APIHEADERS)/eg; +$targets =~s /\{\{api_sources\}\}/renderTemplate($tmpl_msvc_src, @APISOURCES)/eg; +$targets =~s /\{\{ast_headers\}\}/renderTemplate($tmpl_msvc_head, @ASTHEADERS)/eg; +$targets =~s /\{\{ast_sources\}\}/renderTemplate($tmpl_msvc_src, @ASTSOURCES)/eg; +$targets =~s /\{\{parser_headers\}\}/renderTemplate($tmpl_msvc_head, @PARSERHEADERS)/eg; +$targets =~s /\{\{parser_sources\}\}/renderTemplate($tmpl_msvc_src, @PARSERSOURCES)/eg; +$targets =~s /\{\{fn_headers\}\}/renderTemplate($tmpl_msvc_head, @FNHEADERS)/eg; +$targets =~s /\{\{fn_sources\}\}/renderTemplate($tmpl_msvc_src, @FNSOURCES)/eg; +$targets =~s /\{\{headers\}\}/renderTemplate($tmpl_msvc_head, @LIBHEADERS)/eg; +$targets =~s /\{\{sources\}\}/renderTemplate($tmpl_msvc_src, @LIBSOURCES)/eg; warn "Generating ../win/libsass.targets\n"; write_file("../win/libsass.targets", $targets); my $filters = read_file("build-skeletons/libsass.vcxproj.filters"); -$filters =~s /\{\{includes\}\}/renderTemplate($tmpl_msvc_filter_inc, @INCFILES)/eg; -$filters =~s /\{\{headers\}\}/renderTemplate($tmpl_msvc_filter_head, @HPPFILES)/eg; -$filters =~s /\{\{sources\}\}/renderTemplate($tmpl_msvc_filter_src, @SOURCES, @CSOURCES)/eg; +$filters =~s /\{\{api_includes\}\}/renderTemplate($tmpl_msvc_filter_api_inc, @INCFILES)/eg; +$filters =~s /\{\{api_headers\}\}/renderTemplate($tmpl_msvc_filter_api_head, @APIHEADERS)/eg; +$filters =~s /\{\{api_sources\}\}/renderTemplate($tmpl_msvc_filter_api_src, @APISOURCES)/eg; +$filters =~s /\{\{ast_headers\}\}/renderTemplate($tmpl_msvc_filter_ast_head, @ASTHEADERS)/eg; +$filters =~s /\{\{ast_sources\}\}/renderTemplate($tmpl_msvc_filter_ast_src, @ASTSOURCES)/eg; +$filters =~s /\{\{parser_headers\}\}/renderTemplate($tmpl_msvc_filter_parser_head, @PARSERHEADERS)/eg; +$filters =~s /\{\{parser_sources\}\}/renderTemplate($tmpl_msvc_filter_parser_src, @PARSERSOURCES)/eg; +$filters =~s /\{\{fn_headers\}\}/renderTemplate($tmpl_msvc_filter_fn_head, @FNHEADERS)/eg; +$filters =~s /\{\{fn_sources\}\}/renderTemplate($tmpl_msvc_filter_fn_src, @FNSOURCES)/eg; +$filters =~s /\{\{headers\}\}/renderTemplate($tmpl_msvc_filter_head, @LIBHEADERS)/eg; +$filters =~s /\{\{sources\}\}/renderTemplate($tmpl_msvc_filter_src, @LIBSOURCES)/eg; warn "Generating ../win/libsass.vcxproj.filters\n"; write_file("../win/libsass.vcxproj.filters", $filters); diff --git a/win/libsass.sln b/win/libsass.sln index 2a55ad87e3..56d7e1ac87 100644 --- a/win/libsass.sln +++ b/win/libsass.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 14 -VisualStudioVersion = 14.0.25420.1 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.31129.286 MinimumVisualStudioVersion = 10.0.40219.1 Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libsass", "libsass.vcxproj", "{E4030474-AFC9-4CC6-BEB6-D846F631502B}" EndProject @@ -16,24 +16,60 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".SolutionItems", ".Solution ..\res\resource.rc = ..\res\resource.rc EndProjectSection EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "trainer", "trainer.vcxproj", "{C6328AB3-F322-41F1-916B-2F00D19E8B82}" + ProjectSection(ProjectDependencies) = postProject + {E4030474-AFC9-4CC6-BEB6-D846F631502B} = {E4030474-AFC9-4CC6-BEB6-D846F631502B} + EndProjectSection +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Win32 = Debug|Win32 - Debug|Win64 = Debug|Win64 - Release|Win32 = Release|Win32 - Release|Win64 = Release|Win64 + Debug Shared|x64 = Debug Shared|x64 + Debug Shared|x86 = Debug Shared|x86 + Debug Static|x64 = Debug Static|x64 + Debug Static|x86 = Debug Static|x86 + Release Shared|x64 = Release Shared|x64 + Release Shared|x86 = Release Shared|x86 + Release Static|x64 = Release Static|x64 + Release Static|x86 = Release Static|x86 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {E4030474-AFC9-4CC6-BEB6-D846F631502B}.Debug|Win32.ActiveCfg = Debug|Win32 - {E4030474-AFC9-4CC6-BEB6-D846F631502B}.Debug|Win32.Build.0 = Debug|Win32 - {E4030474-AFC9-4CC6-BEB6-D846F631502B}.Debug|Win64.ActiveCfg = Debug|x64 - {E4030474-AFC9-4CC6-BEB6-D846F631502B}.Debug|Win64.Build.0 = Debug|x64 - {E4030474-AFC9-4CC6-BEB6-D846F631502B}.Release|Win32.ActiveCfg = Release|Win32 - {E4030474-AFC9-4CC6-BEB6-D846F631502B}.Release|Win32.Build.0 = Release|Win32 - {E4030474-AFC9-4CC6-BEB6-D846F631502B}.Release|Win64.ActiveCfg = Release|x64 - {E4030474-AFC9-4CC6-BEB6-D846F631502B}.Release|Win64.Build.0 = Release|x64 + {E4030474-AFC9-4CC6-BEB6-D846F631502B}.Debug Shared|x64.ActiveCfg = Debug Shared|x64 + {E4030474-AFC9-4CC6-BEB6-D846F631502B}.Debug Shared|x64.Build.0 = Debug Shared|x64 + {E4030474-AFC9-4CC6-BEB6-D846F631502B}.Debug Shared|x86.ActiveCfg = Debug Shared|Win32 + {E4030474-AFC9-4CC6-BEB6-D846F631502B}.Debug Shared|x86.Build.0 = Debug Shared|Win32 + {E4030474-AFC9-4CC6-BEB6-D846F631502B}.Debug Static|x64.ActiveCfg = Debug Static|x64 + {E4030474-AFC9-4CC6-BEB6-D846F631502B}.Debug Static|x64.Build.0 = Debug Static|x64 + {E4030474-AFC9-4CC6-BEB6-D846F631502B}.Debug Static|x86.ActiveCfg = Debug Static|Win32 + {E4030474-AFC9-4CC6-BEB6-D846F631502B}.Debug Static|x86.Build.0 = Debug Static|Win32 + {E4030474-AFC9-4CC6-BEB6-D846F631502B}.Release Shared|x64.ActiveCfg = Release Shared|x64 + {E4030474-AFC9-4CC6-BEB6-D846F631502B}.Release Shared|x64.Build.0 = Release Shared|x64 + {E4030474-AFC9-4CC6-BEB6-D846F631502B}.Release Shared|x86.ActiveCfg = Release Shared|Win32 + {E4030474-AFC9-4CC6-BEB6-D846F631502B}.Release Shared|x86.Build.0 = Release Shared|Win32 + {E4030474-AFC9-4CC6-BEB6-D846F631502B}.Release Static|x64.ActiveCfg = Release Static|x64 + {E4030474-AFC9-4CC6-BEB6-D846F631502B}.Release Static|x64.Build.0 = Release Static|x64 + {E4030474-AFC9-4CC6-BEB6-D846F631502B}.Release Static|x86.ActiveCfg = Release Static|Win32 + {E4030474-AFC9-4CC6-BEB6-D846F631502B}.Release Static|x86.Build.0 = Release Static|Win32 + {C6328AB3-F322-41F1-916B-2F00D19E8B82}.Debug Shared|x64.ActiveCfg = Debug Shared|x64 + {C6328AB3-F322-41F1-916B-2F00D19E8B82}.Debug Shared|x64.Build.0 = Debug Shared|x64 + {C6328AB3-F322-41F1-916B-2F00D19E8B82}.Debug Shared|x86.ActiveCfg = Debug Shared|Win32 + {C6328AB3-F322-41F1-916B-2F00D19E8B82}.Debug Shared|x86.Build.0 = Debug Shared|Win32 + {C6328AB3-F322-41F1-916B-2F00D19E8B82}.Debug Static|x64.ActiveCfg = Debug Static|x64 + {C6328AB3-F322-41F1-916B-2F00D19E8B82}.Debug Static|x64.Build.0 = Debug Static|x64 + {C6328AB3-F322-41F1-916B-2F00D19E8B82}.Debug Static|x86.ActiveCfg = Debug Static|Win32 + {C6328AB3-F322-41F1-916B-2F00D19E8B82}.Debug Static|x86.Build.0 = Debug Static|Win32 + {C6328AB3-F322-41F1-916B-2F00D19E8B82}.Release Shared|x64.ActiveCfg = Release Shared|x64 + {C6328AB3-F322-41F1-916B-2F00D19E8B82}.Release Shared|x64.Build.0 = Release Shared|x64 + {C6328AB3-F322-41F1-916B-2F00D19E8B82}.Release Shared|x86.ActiveCfg = Release Shared|Win32 + {C6328AB3-F322-41F1-916B-2F00D19E8B82}.Release Shared|x86.Build.0 = Release Shared|Win32 + {C6328AB3-F322-41F1-916B-2F00D19E8B82}.Release Static|x64.ActiveCfg = Release Static|x64 + {C6328AB3-F322-41F1-916B-2F00D19E8B82}.Release Static|x64.Build.0 = Release Static|x64 + {C6328AB3-F322-41F1-916B-2F00D19E8B82}.Release Static|x86.ActiveCfg = Release Static|Win32 + {C6328AB3-F322-41F1-916B-2F00D19E8B82}.Release Static|x86.Build.0 = Release Static|Win32 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {C0017827-65BD-4008-B800-2D93ECB61CCB} + EndGlobalSection EndGlobal diff --git a/win/libsass.sln.DotSettings b/win/libsass.sln.DotSettings deleted file mode 100644 index 405024e159..0000000000 --- a/win/libsass.sln.DotSettings +++ /dev/null @@ -1,9 +0,0 @@ - - ExplicitlyExcluded - ExplicitlyExcluded - ExplicitlyExcluded - ExplicitlyExcluded - ExplicitlyExcluded - ExplicitlyExcluded - ExplicitlyExcluded - ExplicitlyExcluded \ No newline at end of file diff --git a/win/libsass.targets b/win/libsass.targets index a60a27e5c0..9559f4f1e6 100644 --- a/win/libsass.targets +++ b/win/libsass.targets @@ -1,156 +1,226 @@ - + - - - + + + + + + + + + + - + + + + + + + + + + + + + + + + + + + + + + + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - + + - - - + + + + + + + + - - - - - - - - - + + + + + + - - - + - - - - + + - - - + - - - - - - - + + + - + - - - - - - - - + + + + + + + + + + + + + + - - - - - - - - - + + - - - - - - - - + - - - - + + + + - - - - - - + - - - + + + + - + - - - - - - - - - - + + - - - - - - - - - + + + + + + diff --git a/win/libsass.vcxproj b/win/libsass.vcxproj index 02402b38b3..ecfd71cbff 100644 --- a/win/libsass.vcxproj +++ b/win/libsass.vcxproj @@ -1,17 +1,21 @@  - + [NA] ..\src ..\src ..\include - + + {E4030474-AFC9-4CC6-BEB6-D846F631502B} + libsass + + - + %(PreprocessorDefinitions);LIBSASS_VERSION="$(LIBSASS_VERSION)"; @@ -19,45 +23,100 @@ - - - - + + + + - - Debug + + Debug Shared + Win32 + + + Debug Shared + x64 + + + Release Shared + Win32 + + + Release Shared + x64 + + + Debug Static Win32 - - Debug + + Debug Static x64 - - Release + + Release Static Win32 - - Release + + Release Static x64 + + + + + None + Debug + Shared + - - {E4030474-AFC9-4CC6-BEB6-D846F631502B} - Win32Proj - libsass + + v143 - - libsass - Unicode + + v143 + + + v143 - + + v143 + + + v143 + + + v143 + + + v143 + + + v143 + + + Release + Static + + + Release + Shared + + + Debug + Static + + + Debug + Shared + + DynamicLibrary ADD_EXPORTS;$(PreprocessorDefinitions); - + StaticLibrary @@ -72,120 +131,64 @@ v142 - - v143 - - - true - - + + true true + false - + + false false true - - false - true + + $(SolutionDir)build\x86\$(RelType)\$(LibType)\ + + + $(SolutionDir)build\x64\$(RelType)\$(LibType)\ + + + $(OutDir)libsass\ - - - - - - - - - - + - - true - $(SolutionDir)bin\Debug\ - $(SolutionDir)bin\Debug\obj\ - - - true - $(SolutionDir)bin\Debug\ - $(SolutionDir)bin\Debug\obj\ - - - false - $(SolutionDir)bin\ - $(SolutionDir)bin\obj\ - - - false - $(SolutionDir)bin\ - $(SolutionDir)bin\obj\ - - ..\include;%(AdditionalIncludeDirectories) - - - - - - - Level3 - Disabled - WIN32;_DEBUG;_CONSOLE;_LIB;$(PreprocessorDefinitions); - - - Console - true - - - - - - - Level3 - Disabled - WIN32;_DEBUG;_CONSOLE;_LIB;$(PreprocessorDefinitions); - - - Console - true - - - - + true Level3 - - - MaxSpeed - true - true - WIN32;NDEBUG;_CONSOLE;_LIB;$(PreprocessorDefinitions); + true + /MP $(AdditionalOptions) + + $(LIBSASS_INCLUDES_DIR);%(AdditionalIncludeDirectories) + _CONSOLE;_LIB;$(PreprocessorDefinitions); + _DEBUG;$(PreprocessorDefinitions); + NDEBUG;$(PreprocessorDefinitions); + WIN32;$(PreprocessorDefinitions); + Disabled Console true - true - true + /GENPROFILE $(AdditionalOptions) + /USEPROFILE $(AdditionalOptions) + UseLinkTimeCodeGeneration - + - Level3 - - MaxSpeed true true - WIN32;NDEBUG;_CONSOLE;_LIB;$(PreprocessorDefinitions); + Speed + true + true - Console - true true true @@ -194,4 +197,4 @@ - + \ No newline at end of file diff --git a/win/libsass.vcxproj.filters b/win/libsass.vcxproj.filters index 2d6248c96b..df925bd6ae 100644 --- a/win/libsass.vcxproj.filters +++ b/win/libsass.vcxproj.filters @@ -5,312 +5,574 @@ {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms - - {93995380-89BD-4b04-88EB-625FBE52EBFB} + + {93995380-89BD-4b04-88EB-625FBE52EB92} + h;hh;hpp;hxx;hm;in;inl;inc;xsd + + + {93995380-89BD-4b04-88EB-625FBE52EB93} h;hh;hpp;hxx;hm;in;inl;inc;xsd - + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + {93995380-89BD-4b04-88EB-625FBE52EB94} + h;hh;hpp;hxx;hm;in;inl;inc;xsd + + + {4FC737F1-C7A5-4376-A066-2A32D752A2F4} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EB97} + h;hh;hpp;hxx;hm;in;inl;inc;xsd + + + {4FC737F1-C7A5-4376-A066-2A32D752A274} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89B2-4b04-88EB-625FBE52EB97} + h;hh;hpp;hxx;hm;in;inl;inc;xsd + + + {4FC737F1-C735-4376-A066-2A32D752A274} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;hm;in;inl;inc;xsd + + + {4FC737F1-C7A5-4376-A066-2A32D752A2F3} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + - + - LibSass Headers - - - LibSass Headers + API Includes - LibSass Headers + API Includes - - LibSass Headers + + API Includes - - LibSass Headers + + API Includes + + + API Includes + + + API Includes + + + API Includes + + + API Includes + + + API Includes + + + API Includes + + + API Includes + + + API Includes - LibSass Headers + API Includes - LibSass Headers + API Includes + + + + + CAPI Headers + + + CAPI Headers + + + CAPI Headers + + + CAPI Headers + + + CAPI Headers + + + CAPI Headers + + CAPI Headers + + + CAPI Headers + + + CAPI Headers + + + CAPI Headers + + + + + CAPI Sources + + + CAPI Sources + + + CAPI Sources + + + CAPI Sources + + + CAPI Sources + + + CAPI Sources + + + CAPI Sources + + + CAPI Sources + + + CAPI Sources + + + CAPI Sources + + + LibSass Sources + - - - Library Includes + + + AST Headers + + + AST Headers + + + AST Headers + + + AST Headers + + + AST Headers - - Library Includes + + AST Headers - - Library Includes + + AST Headers - - Library Includes + + AST Headers - - Library Includes + + AST Headers - - Library Includes + + AST Headers - - Library Includes + + AST Headers - - Library Includes + + AST Headers - - Library Includes + + AST Headers - - Library Includes + + AST Headers - - Library Includes + + AST Headers + + + + + AST Sources + + + AST Sources + + + AST Sources + + + AST Sources + + + AST Sources + + + AST Sources + + + AST Sources + + + AST Sources + + + AST Sources + + + AST Sources + + + AST Sources + + + AST Sources + + + AST Sources + + + AST Sources + + + + + Parser Headers + + + Parser Headers + + + Parser Headers + + + Parser Headers + + + Parser Headers + + + Parser Headers + + + Parser Headers + + + Parser Headers + + + Parser Headers + + + Parser Headers + + + Parser Headers + + + + + Parser Sources + + + Parser Sources + + + Parser Sources + + + Parser Sources + + + Parser Sources + + + Parser Sources + + + Parser Sources + + + Parser Sources + + + Parser Sources + + + Parser Sources + + + Parser Sources + + + + + Function Headers + + + Function Headers + + + Function Headers + + + Function Headers + + + Function Headers + + + Function Headers + + + Function Headers + + + Function Headers + + + + + Function Sources + + + Function Sources + + + Function Sources + + + Function Sources + + + Function Sources + + + Function Sources + + + Function Sources + + + + + LibSass Headers - - Library Includes + + LibSass Headers - - Library Includes + + LibSass Headers - - Library Includes + + LibSass Headers - - Library Includes + + LibSass Headers - - Library Includes + + LibSass Headers - - Library Includes + + LibSass Headers - - Library Includes + + LibSass Headers - - Library Includes + + LibSass Headers - - Library Includes + + LibSass Headers - - Library Includes + + LibSass Headers - - Library Includes + + LibSass Headers - - Library Includes + + LibSass Headers - - Library Includes + + LibSass Headers - - Library Includes + + LibSass Headers - - Library Includes + + LibSass Headers - - Library Includes + + LibSass Headers - - Library Includes + + LibSass Headers - - Library Includes + + LibSass Headers - - Library Includes + + LibSass Headers - - Library Includes + + LibSass Headers - - Library Includes + + LibSass Headers - - Library Includes + + LibSass Headers - - Library Includes + + LibSass Headers - - Library Includes + + LibSass Headers - - Library Includes + + LibSass Headers - - Library Includes + + LibSass Headers - - Library Includes + + LibSass Headers - - Library Includes + + LibSass Headers - - Library Includes + + LibSass Headers - - Library Includes + + LibSass Headers - - Library Includes + + LibSass Headers - - Library Includes + + LibSass Headers - - Library Includes + + LibSass Headers - - Library Includes + + LibSass Headers - - Library Includes + + LibSass Headers - - Library Includes + + LibSass Headers - - Library Includes + + LibSass Headers - - Library Includes + + LibSass Headers - - Library Includes + + LibSass Headers - - Library Includes + + LibSass Headers - - Library Includes + + LibSass Headers - - Library Includes + + LibSass Headers - - Library Includes + + LibSass Headers - - Library Includes + + LibSass Headers - - Library Includes + + LibSass Headers - - Library Includes + + LibSass Headers - - Library Includes + + LibSass Headers - - Library Includes + + LibSass Headers - - Library Includes + + LibSass Headers - - Library Includes + + LibSass Headers - - Library Includes + + LibSass Headers - - Library Includes + + LibSass Headers - - Library Includes + + LibSass Headers - - Library Includes + + LibSass Headers - - Library Includes + + LibSass Headers - - Library Includes + + LibSass Headers - - Library Includes + + LibSass Headers - - Library Includes + + LibSass Headers - - Library Includes + + LibSass Headers - - Library Includes + + LibSass Headers - - Library Includes + + LibSass Headers - - Library Includes + + LibSass Headers - - - LibSass Sources - - - LibSass Sources - - - LibSass Sources - - - LibSass Sources - - - LibSass Sources - - - LibSass Sources - - - LibSass Sources - - + + LibSass Sources - + LibSass Sources LibSass Sources - - LibSass Sources - - - LibSass Sources - - - LibSass Sources - - - LibSass Sources - - - LibSass Sources - - - LibSass Sources - - - LibSass Sources - - + LibSass Sources @@ -319,19 +581,19 @@ LibSass Sources - + LibSass Sources - + LibSass Sources - + LibSass Sources - + LibSass Sources - + LibSass Sources @@ -340,40 +602,19 @@ LibSass Sources - - LibSass Sources - LibSass Sources LibSass Sources - - LibSass Sources - - - LibSass Sources - - - LibSass Sources - - - LibSass Sources - - + LibSass Sources LibSass Sources - - LibSass Sources - - - LibSass Sources - - + LibSass Sources @@ -388,71 +629,56 @@ LibSass Sources - + LibSass Sources - - LibSass Sources - - - LibSass Sources - - - LibSass Sources - - + LibSass Sources - - LibSass Sources - - - LibSass Sources - - + LibSass Sources - + LibSass Sources - + LibSass Sources - + LibSass Sources - + LibSass Sources - + LibSass Sources - + LibSass Sources - + LibSass Sources LibSass Sources - + LibSass Sources - + LibSass Sources - + LibSass Sources - + LibSass Sources - + LibSass Sources - + LibSass Sources - + \ No newline at end of file diff --git a/win/train.bat b/win/train.bat new file mode 100644 index 0000000000..27bcbbfd50 --- /dev/null +++ b/win/train.bat @@ -0,0 +1,46 @@ +@echo off + +echo This will try to compile an optimize LibSass version +echo You must pass exactly three arguments or funky stuff will happen +echo Usage: train [x86/x64] [Shared/Static] [Release/Debug] + +git clone "https://github.com/mgreter/libsass-specs" --branch bolt-bench build/libsass-spec + +echo Going to build libsass.sln /p:Platform="%1" /p:Configuration="%3 %2" /t:Clean;Build /p:PGO="None" +MSBuild libsass.sln /p:Platform="%1" /p:Configuration="%3 %2" /t:Clean;Build /p:PGO="None" + +echo ###################################################### +echo Get base time for untrained "%1\%3\%2\trainer.exe" +cd build +echo ###################################################### +FOR /L %%A IN (1,1,10) DO ( + %1\%3\%2\trainer.exe libsass-spec\suites\bolt-bench\input.scss 1>nul +) +echo ###################################################### +cd .. + +echo Going to build libsass.sln /p:Platform="%1" /p:Configuration="%3 %2" /t:Clean;Build /p:PGO="Instrument" +MSBuild libsass.sln /p:Platform="%1" /p:Configuration="%3 %2" /t:Clean;Build /p:PGO="Instrument" + +echo ###################################################### +echo Collecting data for "%1\%3\%2\trainer.exe" +cd build +echo ###################################################### +FOR /L %%A IN (1,1,3) DO ( + %1\%3\%2\trainer.exe libsass-spec\suites\bolt-bench\input.scss 1>nul +) +echo ###################################################### +cd .. + +echo Going to build libsass.sln /p:Platform="%1" /p:Configuration="%3 %2" /p:PGO="Optimize" +MSBuild libsass.sln /p:Platform="%1" /p:Configuration="%3 %2" /p:PGO="Optimize" + +echo ###################################################### +echo Get timing after optimizations "%1\%3\%2\trainer.exe" +cd build +echo ###################################################### +FOR /L %%A IN (1,1,10) DO ( + %1\%3\%2\trainer.exe libsass-spec\suites\bolt-bench\input.scss 1>nul +) +echo ###################################################### +cd .. diff --git a/win/trainer.cpp b/win/trainer.cpp new file mode 100644 index 0000000000..5922f171ba --- /dev/null +++ b/win/trainer.cpp @@ -0,0 +1,64 @@ +#include +#include +#include + +uint64_t get_cpu_usage(bool aggregated) { + + FILETIME createTime; + FILETIME exitTime; + FILETIME kernelTime; + FILETIME userTime; + + int rv = -1; + if (aggregated) { + rv = GetProcessTimes(GetCurrentProcess(), + &createTime, &exitTime, &kernelTime, &userTime); + } + else { + rv = GetThreadTimes(GetCurrentThread(), + &createTime, &exitTime, &kernelTime, &userTime); + } + + if (rv != -1) + { + ULONGLONG tcpu = userTime.dwHighDateTime; + tcpu += kernelTime.dwHighDateTime; + ULONGLONG usec = (tcpu << 32) + + kernelTime.dwLowDateTime + + userTime.dwLowDateTime; + return (usec) / 10000; + } + + return 0; +} + +int main(int argc, const char* argv[]) +{ + + uint64_t start = get_cpu_usage(true); + // get the input file from first argument or use default + const char* input = argc > 1 ? argv[1] : "input.scss"; + // create compiler object holding config and states + struct SassCompiler* compiler = sass_make_compiler(); + // add our current path to be searchable for import + sass_compiler_add_include_paths(compiler, "."); + // create the file context and get all related structs + struct SassImport* import = sass_make_file_import(input); + // each compiler must have exactly one entry point + sass_compiler_set_entry_point(compiler, import); + // entry point now passed to compiler, so its reference count was increased + // in order to not leak memory we must release our own usage (usage after is UB) + sass_delete_import(import); // decrease ref-count + + // context is set up, call the compile step now + int status = sass_compiler_execute(compiler, true); + // release allocated memory + sass_delete_compiler(compiler); + + uint64_t delta = get_cpu_usage(true) - start; + fprintf(stderr, "Took %.5fs\n", delta / 1000.0); + + // exit status + return status; + +} diff --git a/win/trainer.vcxproj b/win/trainer.vcxproj new file mode 100644 index 0000000000..2b2e215202 --- /dev/null +++ b/win/trainer.vcxproj @@ -0,0 +1,147 @@ + + + + [NA] + ..\src + ..\src + ..\include + + + + Debug Shared + Win32 + + + Debug Shared + x64 + + + Release Shared + Win32 + + + Release Shared + x64 + + + Debug Static + Win32 + + + Debug Static + x64 + + + Release Static + Win32 + + + Release Static + x64 + + + + 16.0 + Win32Proj + {c6328ab3-f322-41f1-916b-2f00d19e8b82} + trainer + 10.0 + + + + Application + v143 + Unicode + Debug + Shared + None + + + Release + Static + + + Release + Shared + + + Debug + Static + + + Debug + Shared + + + true + true + false + + + false + false + true + + + $(SolutionDir)build\x86\$(RelType)\$(LibType)\ + + + $(SolutionDir)build\x64\$(RelType)\$(LibType)\ + + + $(OutDir)trainer\ + + + + + + + + + + + + + true + Level3 + true + /MP $(AdditionalOptions) + + $(OutDir);%(AdditionalIncludeDirectories) + $(LIBSASS_INCLUDES_DIR);$(LIBSASS_HEADERS_DIR);%(AdditionalIncludeDirectories) + _DEBUG;$(PreprocessorDefinitions); + NDEBUG;$(PreprocessorDefinitions); + WIN32;$(PreprocessorDefinitions); + Disabled + + + Console + true + /GENPROFILE $(AdditionalOptions) + /USEPROFILE $(AdditionalOptions) + UseLinkTimeCodeGeneration + $(OutDir)\libsass.lib;%(AdditionalDependencies) + $(OutDir)\libsass.lib;%(AdditionalDependencies) + + + + + MaxSpeed + true + true + Speed + true + true + + + true + true + + + + + + + + + \ No newline at end of file diff --git a/win/trainer.vcxproj.filters b/win/trainer.vcxproj.filters new file mode 100644 index 0000000000..0cd93870d9 --- /dev/null +++ b/win/trainer.vcxproj.filters @@ -0,0 +1,22 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Source Files + + + \ No newline at end of file